LuaU Globals
Always-available globals
Section titled “Always-available globals”These are present in every script (preamble, handler, grouping) regardless of permissions.
| API | Signature |
|---|---|
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") endreturn json.encode({ source = "...", inline = {} })Native introspection API
Section titled “Native introspection API”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).
| Function | Signature | Description |
|---|---|---|
get_node(id) | int -> string|nil | Returns the node’s full JSON, or nil. |
find_struct_by_name(name) | string -> int|nil | Returns the registry ID of a struct by unqualified name (first match across all parsed sources). |
struct_has_attribute(id, name) | int, string -> bool|nil | True if the struct carries [[codegen::name]] (or any namespaced attribute named name). |
struct_has_method(id, name) | int, string -> bool|nil | True if the struct has a member function, static member function, or constructor with this name. |
struct_has_member(id, name) | int, string -> bool|nil | True if the struct has a member or static member variable with this name. |
struct_has_default_constructor(id) | int -> bool|nil | True if the struct has no constructors, or if any constructor’s parameters all have default values. |
get_base_classes(id) | int -> string|nil | Returns 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|nil | Returns 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.”
Semantic query API
Section titled “Semantic query API”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
_registryIdon 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.
| Function | Signature | Description |
|---|---|---|
type_symbol_of(nodeId) | int -> string|nil | Symbol 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|nil | Registry 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|nil | The node’s full ResolvedType (state + type signature + resolved type symbol). |
symbol_summary(symbolId) | string -> json|nil | Flat SymbolSummary for a symbol ID (kind, qualified name, signature, location, access, parent). |
find_symbols(qualifiedName) | string -> json | JSON array of symbol-ID strings matching a qualified name (all overloads). |
references(symbolId) | string -> json|nil | JSON array of Reference objects — every declaration, definition, and use site of the symbol, sorted by location. |
base_classes(symbolId) | string -> json|nil | JSON array of the symbol’s direct base-class symbol-ID strings. |
members(symbolId) | string -> json|nil | JSON 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.
Context objects
Section titled “Context objects”Preamble context
Section titled “Preamble context”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.
Grouping context
Section titled “Grouping context”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.
Handler context
Section titled “Handler context”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).
Standard LuaU library
Section titled “Standard LuaU library”Available subset (the LuaU sandbox restricts the unsafe parts):
| Library | Use |
|---|---|
table | insert, remove, concat, sort, unpack |
string | format, find, gmatch, gsub, sub, len, rep, upper, lower |
math | floor, ceil, min, max, abs, huge, pi |
| Globals | ipairs, pairs, tostring, tonumber, error, pcall, xpcall, type, select, assert, unpack |
os | opened as part of the base stdlib, then frozen along with every other global by luaL_sandbox |
coroutine | opened 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 |
Permission-gated globals
Section titled “Permission-gated globals”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).
| API | Signature |
|---|---|
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.
require
Section titled “require”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 | |
|---|---|
| Argument | Must be a string. A component that is not installed raises an error naming what is. |
| Return | Whatever the component returned. A component that returns nothing is an error. |
| Identity | require twice in one run yields the same value; a fresh one per entity, matching how shared scripts have always behaved. |
| Cycles | Detected and reported as a cycle, with the chain. |
| Availability | Transform, 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.
Absent globals
Section titled “Absent globals”The following are explicitly not provided:
package, and any module search mechanism —requireresolves only from the fixed set aboveio(file I/O) — there is no filesystem module of any kind, and nofscapabilitydebug(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.
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 tojson.decode. See Semantic Queries.