Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
193 commits
Select commit Hold shift + click to select a range
816ee8b
fix(infer): type the parameters of functions written in table constru…
loaf-scripts Oct 1, 2026
7e44e84
fix(luacats): resolve self in doc types to the class of the function'…
loaf-scripts Oct 1, 2026
0bb9e85
fix(luacats): read async fun(...) types
loaf-scripts Oct 1, 2026
a7f3178
fix(luacats): continue @param, @return, @type and @field with ---| lines
loaf-scripts Oct 1, 2026
62149e4
fix(luacats): show the descriptions of ---| values in completion and …
loaf-scripts Oct 1, 2026
4485612
fix(infer): read undeclared fields through the [key] indices of table…
loaf-scripts Oct 1, 2026
75f9406
fix(infer): bind backtick generics to the type a string argument names
loaf-scripts Oct 1, 2026
5568615
fix(workspace): index a file again when it declares other names
loaf-scripts Oct 1, 2026
c59e7cb
feat(infer): narrow locals by type() checks and comparisons with lite…
loaf-scripts Oct 1, 2026
2e70fe5
feat(infer): keep the literals of a local declared from another variable
loaf-scripts Oct 1, 2026
30152ef
feat(infer): apply ---@cast lines to the locals they name
loaf-scripts Oct 1, 2026
e7728eb
feat(rules): add cast-type-mismatch
loaf-scripts Oct 1, 2026
7d70ec5
feat(rules): add count-down-loop
loaf-scripts Oct 1, 2026
34b7a6c
feat(rules): add duplicate-set-field
loaf-scripts Oct 1, 2026
5d64c34
feat(rules): add undefined-doc-param
loaf-scripts Oct 1, 2026
907ae74
feat(rules): add duplicate-doc-alias
loaf-scripts Oct 1, 2026
7889e3d
feat(rules): add duplicate-doc-field
loaf-scripts Oct 1, 2026
e7fe661
feat(rules): add invisible
loaf-scripts Oct 1, 2026
a09a859
feat(completion): offer the members of enums
loaf-scripts Oct 1, 2026
c619c9b
feat(definition): go to type definition
loaf-scripts Oct 1, 2026
a4bb69c
feat(definition): go to implementation
loaf-scripts Oct 1, 2026
b49b47c
feat(infer): infer classes built with setmetatable and __index
loaf-scripts Oct 1, 2026
2a2b85e
fix(luacats): read class parents as types
loaf-scripts Oct 1, 2026
8fc0e38
feat(infer): read generic classes through their type arguments
loaf-scripts Oct 1, 2026
394aadb
feat(infer): resolve generic aliases with their type arguments
loaf-scripts Oct 1, 2026
ccade41
feat(infer): read table types named as class parents
loaf-scripts Oct 1, 2026
225a85c
feat(rules): add redundant-parameter
loaf-scripts Oct 1, 2026
b4333a2
feat(rules): add param-type-mismatch
loaf-scripts Oct 1, 2026
b9139dd
feat(rules): add redundant-return-value
loaf-scripts Oct 1, 2026
7debbc4
feat(rules): add discard-returns
loaf-scripts Oct 1, 2026
50a3d17
feat(infer): narrow locals that a condition reads from
loaf-scripts Oct 1, 2026
c1f6aa4
feat(rules): add need-check-nil
loaf-scripts Oct 1, 2026
56a6a02
fix(luacats): pass over plain comments and directives inside doc bloc…
loaf-scripts Oct 1, 2026
beb0a48
fix(index): keep members set through self before their global is indexed
loaf-scripts Oct 1, 2026
65bb49b
fix(index): index closed files again when the globals they read change
loaf-scripts Oct 1, 2026
6b3b8e2
fix(ls): read a `@type` below a `@class` as typing a value of the class
loaf-scripts Oct 1, 2026
14ab406
fix(infer): call values whose type is an alias of a function type
loaf-scripts Oct 1, 2026
ffab6ab
fix(infer): line up calls through exports with the parameters the pro…
loaf-scripts Oct 1, 2026
698b325
docs: say a literal in a local counts by its kind in impossible-compa…
loaf-scripts Oct 2, 2026
41d84e8
fix(rules): read the parameters of callbacks as their callee declares…
loaf-scripts Oct 2, 2026
dccbeaa
test(lsp): drop a leftover assertion message
loaf-scripts Oct 2, 2026
75b96a0
fix(rules): let a class index of string literals take only those keys
loaf-scripts Oct 2, 2026
02d13da
fix(rules): check calls of locals and fields typed as functions with …
loaf-scripts Oct 2, 2026
5642716
feat(rules): check the entries of tables typed as shapes, arrays and …
loaf-scripts Oct 2, 2026
7ea22d4
feat(rules): check what functions written as a typed function return
loaf-scripts Oct 2, 2026
792dd74
feat(infer): type operations on class values by their @operator lines
loaf-scripts Oct 2, 2026
44554e3
fix(infer): give a field that several assignments set the union of th…
loaf-scripts Oct 2, 2026
4e643c7
fix(inlay): name the parameters of the overload a call picks
loaf-scripts Oct 2, 2026
2582c7c
fix(signature): show the docs of the local a call names, not those of…
loaf-scripts Oct 2, 2026
0e4b508
fix(infer): read class fields from the declarations of the file's own…
loaf-scripts Oct 2, 2026
27d583e
fix(infer): call the @overload of a class that a method field holds
loaf-scripts Oct 2, 2026
439f9d7
fix(infer): read self in the type of a local as unknown
loaf-scripts Oct 2, 2026
eb2ca9e
fix(infer): type the parameters of functions defined for a declared f…
loaf-scripts Oct 2, 2026
bffda2f
fix(index): pass the first value of an export registered from a metho…
loaf-scripts Oct 2, 2026
1c07286
fix(natives): keep the signatures of server natives named like client…
loaf-scripts Oct 2, 2026
949418e
fix(infer): narrow elseif conditions by the conditions before them
loaf-scripts Oct 2, 2026
b751619
fix(completion): slice the identifier before the cursor at a char bou…
loaf-scripts Oct 2, 2026
524d1a6
fix(directives): split directive parts on multibyte whitespace withou…
loaf-scripts Oct 2, 2026
5a3ed68
perf(rules): read each class once when walking class parents
loaf-scripts Oct 3, 2026
a17b5a9
fix(workspace): index a new file again when it reads what it declares
loaf-scripts Oct 3, 2026
fbfa693
fix(index): stop types that functions infer from themselves from growing
loaf-scripts Oct 3, 2026
24da0f0
fix(index): leave the same index whatever order files were indexed in
loaf-scripts Oct 3, 2026
7fd7565
fix(analysis): follow symlinked folders as FiveM does
loaf-scripts Oct 3, 2026
2494b7b
feat(infer): narrow locals that are assigned again
loaf-scripts Oct 3, 2026
50e2eeb
feat(rules): check locals that are assigned again in need-check-nil
loaf-scripts Oct 3, 2026
3937502
feat(infer): narrow fields of locals
loaf-scripts Oct 3, 2026
4de39ba
fix(infer): type `and` and `or` by what each side can be
loaf-scripts Oct 3, 2026
4219c4b
feat(rules): report values of unknown type that declared types take i…
loaf-scripts Oct 3, 2026
16937e2
feat(hover): write out the aliases that function signatures and field…
loaf-scripts Oct 3, 2026
a02c9a2
feat(rules): report local statements that leave names without a value
loaf-scripts Oct 3, 2026
63503c6
feat(rules): report nil, optional and false arguments and check nativ…
loaf-scripts Oct 3, 2026
39fba51
fix(index): add fields only through the names that own their tables
loaf-scripts Oct 3, 2026
d02043a
feat(rules): report fields set through values whose type lacks them i…
loaf-scripts Oct 3, 2026
ba9b721
feat(rules): check tables typed as a union of classes and shapes, or …
loaf-scripts Oct 3, 2026
c0f4f24
fix(rules): require only the fields a (partial) class declares itself…
loaf-scripts Oct 3, 2026
9100f83
feat(rules): check tables held in arrays, maps and tuples in missing-…
loaf-scripts Oct 3, 2026
c52be85
feat(rules): add opt-in checks for undocumented functions
loaf-scripts Oct 3, 2026
0afe22d
feat(rules): report fields read from values whose type lacks them in …
loaf-scripts Oct 3, 2026
c3f650c
fix(hover): write out the table types fields declare in overviews
loaf-scripts Oct 4, 2026
3854cdc
feat(rules): add cast-local-type
loaf-scripts Oct 4, 2026
eae97b1
feat(rules): report server-only Lua libraries used on the client
loaf-scripts Oct 4, 2026
b6deb02
fix(analysis): make loop variables visible from the end of the loop h…
loaf-scripts Oct 4, 2026
485121a
feat(rules): add newline-call
loaf-scripts Oct 4, 2026
93200c1
feat(rules): add trailing-space
loaf-scripts Oct 4, 2026
7344c02
feat(rules): add redundant-return
loaf-scripts Oct 4, 2026
d56a433
feat(rules): add duplicate-doc-param
loaf-scripts Oct 4, 2026
513e4c1
feat(rules): add doc-field-no-class
loaf-scripts Oct 4, 2026
75d86ec
feat(rules): add unknown-operator
loaf-scripts Oct 4, 2026
e222c44
feat(rules): add unknown-cast-variable
loaf-scripts Oct 4, 2026
91cf193
feat(rules): add unknown-diag-code
loaf-scripts Oct 4, 2026
a005eab
feat(rules): add close-non-object
loaf-scripts Oct 4, 2026
c08f02e
feat(rules): add circle-doc-class
loaf-scripts Oct 4, 2026
ede2347
feat(hover): show more or less of hovers at the level lua-language-se…
loaf-scripts Oct 4, 2026
dbb6d3a
feat(rules): report code after an if none of whose branches finishes …
loaf-scripts Oct 4, 2026
4bbd45c
feat(rules): report your own @deprecated in deprecated
loaf-scripts Oct 4, 2026
7c0e90c
feat(rules): report table locals whose fields are only set in unused-…
loaf-scripts Oct 4, 2026
89e7b31
feat(rules): report local functions only used by unused functions in …
loaf-scripts Oct 4, 2026
f9a6d5c
feat(rules): add unused-vararg
loaf-scripts Oct 4, 2026
770ceff
fix(rules): report a parameter redeclared at the top of its function …
loaf-scripts Oct 4, 2026
504838c
feat(rules): add newfield-call
loaf-scripts Oct 4, 2026
fa519bf
feat(rules): add ambiguity-1
loaf-scripts Oct 4, 2026
26eb25b
feat(rules): add global-in-nil-env
loaf-scripts Oct 4, 2026
9433e63
fix(rules): leave ---@meta stubs to lowercase-global, implicit-global…
loaf-scripts Oct 4, 2026
29df7e1
fix(analysis): skip implicit-global in opaque resources
loaf-scripts Oct 4, 2026
b10f467
fix(infer): take the nearest definition of a global
loaf-scripts Oct 4, 2026
24726ca
fix(analysis): let files that other resources load see their globals
loaf-scripts Oct 4, 2026
422bd0c
fix(luacats): read [K]?: V table indices as optional
loaf-scripts Oct 4, 2026
3c3176e
fix(infer): read the ...T a call returns as T in locals and table fields
loaf-scripts Oct 4, 2026
486f4cd
fix(stubs): link the values load and string.find return, and give os.…
loaf-scripts Oct 4, 2026
0e4bb2e
fix(infer): keep the members a definition file sets on a function, as…
loaf-scripts Oct 4, 2026
aab7101
fix(index): keep members set under a side guard, as ox_lib's server l…
loaf-scripts Oct 4, 2026
6a80c87
fix(infer): give nil for calls of functions that return nothing, and …
loaf-scripts Oct 4, 2026
15b0307
feat(rules): check indexed reads and the nil that functions without @…
loaf-scripts Oct 4, 2026
6cfa517
fix(rules): check each type a union lists in assign-type-mismatch and…
loaf-scripts Oct 4, 2026
24a2eb5
fix(rules): count the values a call at the end of the arguments gives…
loaf-scripts Oct 4, 2026
7810e9b
feat(diagnostics): add a strict setting for TypeScript-style checks
loaf-scripts Oct 4, 2026
dba26a7
feat(ls): add an sql type and list the functions that take it
loaf-scripts Oct 5, 2026
c58f5dd
feat(events): add the events that FiveM itself triggers
loaf-scripts Oct 5, 2026
0ad317f
feat(natives): update the natives and return their pointers as the Lu…
loaf-scripts Oct 5, 2026
931bbd1
feat(natives): type the functions that natives call back
loaf-scripts Oct 5, 2026
06a0aa6
fix(stubs): match the CfxLua runtime
loaf-scripts Oct 5, 2026
2eaae8d
fix(rules): skip missing-fields in ---@meta files
loaf-scripts Oct 5, 2026
48925e2
feat(rules): report names of one side in shared scripts in native-wro…
loaf-scripts Oct 6, 2026
08c2939
feat(rules): check fields, keys and call values in need-check-nil wit…
loaf-scripts Oct 6, 2026
d14ee38
feat(rules): report typed locals that nothing gives a value in assign…
loaf-scripts Oct 6, 2026
6754db1
feat(infer): narrow the fields of globals like those of locals
loaf-scripts Oct 6, 2026
49c99d2
fix(infer): give what `assert` returns without `nil` and `false`
loaf-scripts Oct 6, 2026
b093d32
fix(rules): count what generics return as the declared arguments bind…
loaf-scripts Oct 6, 2026
d89b47b
fix(server): report the files nobody has open when the client sends n…
loaf-scripts Oct 6, 2026
f9036ec
fix(server): clear what a file showed under the index's URI once it i…
loaf-scripts Oct 6, 2026
a9f385f
feat(infer): type a function that a return passes from the @return of…
loaf-scripts Oct 6, 2026
af90bfd
feat(infer): type a function given by or and and from what the statem…
loaf-scripts Oct 6, 2026
69fe365
feat(infer): read the fields that the literals a local holds name
loaf-scripts Oct 6, 2026
5cdad68
feat(infer): narrow by equality with a local by what its type rules out
loaf-scripts Oct 6, 2026
5742595
fix(infer): know neither keys nor values in a loop over a generic
loaf-scripts Oct 6, 2026
8a8966d
feat(infer): give back the number tonumber is given
loaf-scripts Oct 6, 2026
3c48bc7
feat(data): type the tables that natives return
loaf-scripts Oct 6, 2026
14de4ac
fix(rules): take type lines and unknown written out at their word in …
loaf-scripts Oct 6, 2026
3aac067
feat(ls): check folders from the command line with qbx-lua-ls --check
loaf-scripts Oct 6, 2026
e787478
feat(rules): check the entries that assignments store in declared tables
loaf-scripts Oct 7, 2026
c0d5f7b
feat(rules): check what table.insert adds against the entries of the …
loaf-scripts Oct 7, 2026
fd41a9d
fix(infer): cache only the types that inference did not cut short
loaf-scripts Oct 7, 2026
79dae35
fix(infer): keep a generic as it is through the guards on it
loaf-scripts Oct 7, 2026
788415b
fix(rules): let a cast widen the declared type
loaf-scripts Oct 7, 2026
a57fa7b
feat(rules): let locals own the tables they build inside functions
loaf-scripts Oct 7, 2026
d50ad64
feat(infer): give a function the signatures of each of its definitions
loaf-scripts Oct 7, 2026
f654f99
feat(ls): add `---@extend` to give a function signatures from other f…
loaf-scripts Oct 7, 2026
c7d2076
fix(infer): type a function written as a value with the doc comment o…
loaf-scripts Oct 7, 2026
8767cf8
fix(stubs): give `RegisterNetEvent` with a handler the handle it returns
loaf-scripts Oct 7, 2026
f563547
fix(rules): report undefined types in inline `--[[@as T]]` casts
loaf-scripts Oct 7, 2026
8c60340
fix(rules): let strict entries that nil removes take values that may …
loaf-scripts Oct 7, 2026
3e18b9f
feat(infer): narrow through locals that hold a condition
loaf-scripts Oct 8, 2026
8a7c919
fix(rules): leave out the parameters a handler's doc comment marks op…
loaf-scripts Oct 13, 2026
d1262ba
feat(infer): read a side of `and` and `or` that tells nothing about a…
loaf-scripts Oct 13, 2026
6473009
fix(infer): bind the generics of `{ [K]: V }` as those of `table<K, V>`
loaf-scripts Oct 13, 2026
b0471db
test(ls): await the diagnostics of open documents in the lsp tests
ChatDisabled Oct 21, 2026
43d4c9c
fix(server): leave locale keys of resources checked in part unreported
ChatDisabled Oct 21, 2026
42d426d
fix(analysis): read a linked folder inside another linked folder once
ChatDisabled Oct 21, 2026
384d86d
fix(server): pass on what open documents change after a change on disk
ChatDisabled Oct 21, 2026
f181cf0
fix(rules): leave tables and fields assigned through globals to strict
ChatDisabled Oct 21, 2026
c6849a2
fix(rules): let nil decide no generic and pass for one
ChatDisabled Oct 21, 2026
b60725e
fix(rules): skip truthy-guarded reads of locals that only hold false
ChatDisabled Oct 21, 2026
ed99c0b
fix(rules): bind generics from declared types for annotation-only checks
ChatDisabled Oct 21, 2026
ff7744d
fix(rules): stop test stubs from overriding runtime function signatures
ChatDisabled Oct 21, 2026
9902fc0
fix(rules): treat os and io in shared scripts like other one-sided names
ChatDisabled Oct 21, 2026
35f9542
fix(rules): put the fallback of a side-guarded and/or on the other side
ChatDisabled Oct 21, 2026
7120205
fix(config): stop .luarc.json from turning off undefined-field
ChatDisabled Oct 21, 2026
ce43407
fix(rules): end ---@diagnostic codes at the first one without a comma
ChatDisabled Oct 21, 2026
b943db4
fix(stubs): drop dofile and loadfile, which fivem removes
ChatDisabled Oct 21, 2026
ad4ea46
fix(stubs): make the delay of Wait optional, as the scheduler allows
ChatDisabled Oct 21, 2026
fbcbaad
fix(stubs): give table.remove the entry type of the list without nil
ChatDisabled Oct 21, 2026
8688d22
fix(stubs): type tonumber with a base as an integer, as lua-ls does
ChatDisabled Oct 21, 2026
962e512
fix(events): add the game and ped events the fivem server triggers
ChatDisabled Oct 21, 2026
0f7dbf8
fix(infer): type natives in side-guarded shared code by the guarded side
ChatDisabled Oct 21, 2026
a325b9f
fix(luacats): read '"left"' quoted literals as LuaLS does
ChatDisabled Oct 21, 2026
fdce3ab
fix(luacats): skip whole characters outside ASCII in function types
ChatDisabled Oct 21, 2026
932f320
fix(luacats): read named returns of function types as LuaLS does
ChatDisabled Oct 21, 2026
a10de6b
fix(luacats): read the type parameters of fun<T>(...) types
ChatDisabled Oct 21, 2026
cfb9965
fix(luacats): read the ? of ---@field [K]? V as an optional field
ChatDisabled Oct 21, 2026
1992302
fix(rules): stop circle-doc-class from following alias parents
ChatDisabled Oct 21, 2026
0070872
fix(rules): let methods of a generic class name its type parameters
ChatDisabled Oct 21, 2026
032fb8b
fix(infer): give no-op stubs the returns of their other definitions
ChatDisabled Oct 21, 2026
51dd972
fix(infer): keep the index of metatables set on values of declared types
ChatDisabled Oct 21, 2026
1d05dc2
fix(infer): prefer overloads that type more arguments on a tie
ChatDisabled Oct 21, 2026
f14e016
fix(index): keep library additions to runtime tables a resource extends
ChatDisabled Oct 21, 2026
d0bbf3f
fix(returns): skip function literals typed by a generic @return
ChatDisabled Oct 21, 2026
7ecc153
fix(infer): type table field functions by the @return above the field
ChatDisabled Oct 21, 2026
5aca964
fix(rules): stop a negated loop step from overflowing in sequence checks
ChatDisabled Oct 21, 2026
34c4e2e
fix(narrow): let a string a native returns be missing in an and side
ChatDisabled Oct 21, 2026
c21b74c
fix(rules): give copies of guarded values their other literals
ChatDisabled Oct 21, 2026
ca16045
fix(rules): report reassigned typed locals as cast-local-type
ChatDisabled Oct 21, 2026
c4e6b60
fix(narrow): let calls that may run code undo guards for comparisons
ChatDisabled Oct 21, 2026
fd82d54
fix(narrow): keep guards on locals across a yield outside strict mode
ChatDisabled Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,404 changes: 748 additions & 656 deletions crates/qbx_fivem_data/data/natives.tsv

Large diffs are not rendered by default.

363 changes: 225 additions & 138 deletions crates/qbx_fivem_data/data/natives_docs.tsv

Large diffs are not rendered by default.

229 changes: 217 additions & 12 deletions crates/qbx_fivem_data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct Native {
returns: &'static str,
params: &'static str,
pub alias_of: Option<&'static str>,
/// The hash, returned values and parameters of the server native of the same name, when they
/// differ from those of the client one: `GetAllVehicles` returns a table on the server.
server: Option<(&'static str, &'static str, &'static str)>,
}

impl Native {
Expand All @@ -58,20 +61,52 @@ impl Native {
let returns = cols.next()?;
let params = cols.next()?;
let alias_of = cols.next().filter(|a| !a.is_empty());
Some(Self { name, side, namespace, hash, returns, params, alias_of })
let server = match (cols.next(), cols.next(), cols.next()) {
(Some(hash), Some(returns), Some(params)) => Some((hash, returns, params)),
_ => None,
};
Some(Self { name, side, namespace, hash, returns, params, alias_of, server })
}

/// The native that code on `side` calls: on the server, the server native of the same name
/// when its signature differs from the client one.
pub fn on(self, side: Option<Side>) -> Self {
match (side, self.server) {
(Some(Side::Server), Some((hash, returns, params))) => {
Self { side: Side::Server, namespace: "CFX", hash, returns, params, server: None, ..self }
}
_ => self,
}
}

/// The types of the values it returns. A `table` has the LuaCATS type of `RETURN_TYPES` where
/// that table gives one.
pub fn returns(&self) -> impl Iterator<Item = &'static str> {
self.returns.split(',').filter(|r| !r.is_empty())
let native = self.name;
self.returns.split(',').filter(|r| !r.is_empty()).map(move |ty| match ty {
"table" => RETURN_TYPES.iter().find(|(owner, _)| *owner == native).map_or(ty, |(_, ty)| ty),
_ => ty,
})
}

/// `(name, type)` pairs.
pub fn params(&self) -> impl Iterator<Item = (&'static str, &'static str)> {
self.params.split(',').filter_map(|p| p.split_once(':'))
/// `(name, type, optional)` triples. The table writes an optional parameter `name?`: the
/// pointer that the Lua wrapper takes as an initial value, which nil leaves 0. A function
/// parameter has the LuaCATS type of `CALLBACK_TYPES` where that table gives one.
pub fn params(&self) -> impl Iterator<Item = (&'static str, &'static str, bool)> {
let native = self.name;
self.params.split(',').filter_map(|p| p.split_once(':')).map(move |(name, ty)| {
let (name, optional) = match name.strip_suffix('?') {
Some(name) => (name, true),
None => (name, false),
};
let callback = CALLBACK_TYPES.iter().find(|(owner, param, _)| *owner == native && *param == name);
(name, callback.map_or(ty, |(_, _, ty)| ty), optional)
})
}

pub fn signature(&self) -> String {
let params: Vec<String> = self.params().map(|(n, t)| format!("{n}: {t}")).collect();
let params: Vec<String> =
self.params().map(|(n, t, optional)| format!("{n}{}: {t}", if optional { "?" } else { "" })).collect();
let returns: Vec<&str> = self.returns().collect();
let mut out = format!("function {}({})", self.name, params.join(", "));
if !returns.is_empty() {
Expand All @@ -82,6 +117,51 @@ impl Native {
}
}

/// The LuaCATS types of the function parameters of natives, which the native data only calls
/// `function`, as their documentation describes the functions: `(native, parameter, type)`. The
/// classes they name are declared in the stubs. `RegisterRawNuiCallback` is left out, as nothing
/// documents what its callback receives.
pub static CALLBACK_TYPES: &[(&str, &str, &str)] = &[
("RegisterCommand", "handler", "fun(source: integer, args: string[], rawCommand: string)"),
(
"AddStateBagChangeHandler",
"handler",
"fun(bagName: string, key: string, value: any, reserved: number, replicated: boolean)",
),
("AddConvarChangeListener", "handler", "fun(conVarName: string, reserved: any)"),
("RegisterConsoleListener", "listener", "fun(channel: string, message: string)"),
("SetHttpHandler", "handler", "fun(request: HttpHandlerRequest, response: HttpHandlerResponse)"),
("RegisterNuiCallback", "callback", "fun(data: unknown, cb: fun(response: any))"),
("RegisterRawKeymap", "onKeyDown", "fun()|nil"),
("RegisterRawKeymap", "onKeyUp", "fun()|nil"),
("ScanResourceRoot", "callback", "fun(results: table)"),
("RegisterResourceBuildTaskFactory", "factoryFn", "fun(): ResourceBuildTask"),
("RegisterArchetypes", "factory", "fun(): table[]"),
];

/// The LuaCATS types of the tables that natives return, which the native data only calls `table`:
/// `(native, type)`. Each is what the native serializes in the source of the runtime: a list of
/// handles, hashes or names, of `[a, b]` pairs, or of command objects. `GetStateBagValue` gives the
/// stored value itself, which may be of any type.
pub static RETURN_TYPES: &[(&str, &str)] = &[
("DoorSystemGetActive", "[Hash, Object][]"),
("GetActivePlayers", "Player[]"),
("GetAllObjects", "Object[]"),
("GetAllPeds", "Ped[]"),
("GetAllRopes", "integer[]"),
("GetAllTrackJunctions", "integer[]"),
("GetAllVehicleModels", "string[]"),
("GetAllVehicles", "Vehicle[]"),
("GetClosestTrackNodes", "[integer, integer][]"),
("GetEntitiesInRadius", "Entity[]"),
("GetGamePool", "Entity[]"),
("GetPedDecorations", "[Hash, Hash][]"),
("GetRegisteredCommands", "{ name: string, resource: string, arity: integer }[]"),
("GetResourceCommands", "{ name: string, resource: string, arity: integer }[]"),
("GetStateBagKeys", "string[]"),
("GetStateBagValue", "any"),
];

struct LineTable {
text: &'static str,
starts: Vec<u32>,
Expand Down Expand Up @@ -171,17 +251,63 @@ pub fn native_docs(name: &str) -> Option<String> {
pub struct Stub {
pub name: &'static str,
pub side: Side,
/// Whether it declares Lua standard libraries rather than CfxLua additions.
pub library: bool,
pub source: &'static str,
}

const fn stub(name: &'static str, side: Side, library: bool, source: &'static str) -> Stub {
Stub { name, side, library, source }
}

/// The runtime definitions. FiveM only opens the `io` and `os` libraries on the server
/// (`IS_FXSERVER` in citizen-scripting-lua's `LuaScriptRuntime.cpp`), so `lua54_server.lua` holds
/// them; stubs extending a table come after the one declaring it.
pub static STUBS: &[Stub] = &[
Stub { name: "lua54.lua", side: Side::Shared, source: include_str!("../stubs/lua54.lua") },
Stub { name: "cfx.lua", side: Side::Shared, source: include_str!("../stubs/cfx.lua") },
Stub { name: "glm.lua", side: Side::Shared, source: include_str!("../stubs/glm.lua") },
Stub { name: "cfx_client.lua", side: Side::Client, source: include_str!("../stubs/cfx_client.lua") },
Stub { name: "cfx_server.lua", side: Side::Server, source: include_str!("../stubs/cfx_server.lua") },
stub("lua54.lua", Side::Shared, true, include_str!("../stubs/lua54.lua")),
stub("lua54_server.lua", Side::Server, true, include_str!("../stubs/lua54_server.lua")),
stub("cfx.lua", Side::Shared, false, include_str!("../stubs/cfx.lua")),
stub("glm.lua", Side::Shared, false, include_str!("../stubs/glm.lua")),
stub("cfx_client.lua", Side::Client, false, include_str!("../stubs/cfx_client.lua")),
stub("cfx_server.lua", Side::Server, false, include_str!("../stubs/cfx_server.lua")),
stub("cfx_events.lua", Side::Shared, false, EVENTS_STUB),
];

static EVENTS_STUB: &str = include_str!("../stubs/cfx_events.lua");

/// An event that FiveM itself triggers, such as `onResourceStop` or `playerDropped`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BuiltinEvent {
pub name: &'static str,
/// The only side that triggers it, or `Shared` when both do.
pub side: Side,
}

/// The events that FiveM itself triggers, as the `---@field` lines of the `CfxEvents` class in
/// `cfx_events.lua` declare them: a `(client)` or `(server)` before the name scopes one to that side.
pub fn builtin_events() -> &'static [BuiltinEvent] {
static EVENTS: OnceLock<Vec<BuiltinEvent>> = OnceLock::new();
EVENTS.get_or_init(|| {
let class = EVENTS_STUB.lines().skip_while(|line| *line != "---@class CfxEvents").skip(1);
class
.map_while(|line| line.strip_prefix("---@field "))
.filter_map(|field| {
let (side, field) = match field.split_once(") ") {
Some(("(client", rest)) => (Side::Client, rest),
Some(("(server", rest)) => (Side::Server, rest),
_ => (Side::Shared, field),
};
Some(BuiltinEvent { name: field.split(' ').next()?, side })
})
.collect()
})
}

/// The event `name` when FiveM itself triggers it.
pub fn builtin_event(name: &str) -> Option<BuiltinEvent> {
builtin_events().iter().find(|event| event.name == name).copied()
}

pub struct KnownImport {
pub path: &'static str,
pub globals: &'static [&'static str],
Expand Down Expand Up @@ -226,12 +352,76 @@ mod tests {
fn looks_up_natives() {
let native = native("GetEntityCoords").unwrap();
assert_eq!(native.returns().collect::<Vec<_>>(), ["vector3"]);
assert_eq!(native.params().next(), Some(("entity", "Entity")));
assert_eq!(native.params().next(), Some(("entity", "Entity", false)));
assert_eq!(super::native("GetPlayerIdentifier").unwrap().side, Side::Server);
assert!(super::native("NotARealNative").is_none());
assert!(native_count() > 6000);
}

#[test]
fn server_natives_keep_their_own_signature() {
let vehicles = native("GetAllVehicles").unwrap();
assert_eq!(vehicles.on(Some(Side::Client)).returns().collect::<Vec<_>>(), ["integer", "integer"]);
assert_eq!(vehicles.on(None).returns().collect::<Vec<_>>(), ["integer", "integer"]);
let server = vehicles.on(Some(Side::Server));
assert_eq!(server.returns().collect::<Vec<_>>(), ["Vehicle[]"]);
assert_eq!((server.side, server.namespace, server.hash), (Side::Server, "CFX", "0x332169F5"));
let weapon = native("GetCurrentPedWeapon").unwrap().on(Some(Side::Server));
assert_eq!(weapon.signature(), "function GetCurrentPedWeapon(ped: Ped): Hash");
// A native without a server signature of its own is the same everywhere.
let health = native("GetEntityHealth").unwrap();
assert_eq!(health.on(Some(Side::Server)).hash, health.hash);
}

#[test]
fn pointers_follow_the_lua_wrappers() {
let shape = |name: &str| {
let native = native(name).unwrap();
(native.params().collect::<Vec<_>>(), native.returns().collect::<Vec<_>>())
};
// Two pointers: both are returned, neither is an argument.
assert_eq!(shape("GetGroupSize"), (vec![("groupID", "integer", false)], vec!["integer", "integer"]));
// The one pointer, last: an optional initial value, and returned.
assert_eq!(
shape("GetEntityPlayerIsFreeAimingAt"),
(vec![("player", "Player", false), ("entity", "Entity", true)], vec!["boolean", "Entity"])
);
// A native that releases the handle it is given needs one.
assert_eq!(shape("DeleteEntity"), (vec![("entity", "Entity", false)], vec!["Entity"]));
let server = native("DeleteEntity").unwrap().on(Some(Side::Server));
assert_eq!(server.returns().count(), 0, "the server native takes the handle itself");
// The wrapper reads an Any* as an integer, and passes none it does not take.
assert_eq!(shape("DataarrayGetInt"), (vec![("arrayIndex", "integer", false)], vec!["integer", "integer"]));
assert_eq!(
native("GetEntityPlayerIsFreeAimingAt").unwrap().signature(),
"function GetEntityPlayerIsFreeAimingAt(player: Player, entity?: Entity): boolean, Entity"
);
}

#[test]
fn callback_types_name_function_parameters() {
for (name, param, ty) in CALLBACK_TYPES {
let native = native(name).unwrap_or_else(|| panic!("{name} is no native"));
let declared = native.params.split(',').filter_map(|p| p.split_once(':')).find(|(n, _)| n == param);
assert_eq!(declared.map(|(_, ty)| ty), Some("function"), "{name}({param})");
assert!(native.params().any(|(n, t, _)| n == *param && t == *ty));
}
}

#[test]
fn return_types_name_tables_that_natives_return() {
for (name, ty) in RETURN_TYPES {
let native = native(name).unwrap_or_else(|| panic!("{name} is no native"));
let returns = native.server.map_or(native.returns, |(_, server, _)| server);
assert!(
native.returns.split(',').chain(returns.split(',')).any(|r| r == "table"),
"{name} returns no table"
);
let side = native.server.map(|_| Side::Server);
assert!(native.on(side).returns().any(|r| r == *ty), "{name}");
}
}

#[test]
fn natives_are_sorted_for_binary_search() {
let names: Vec<&str> = natives().map(|n| n.name).collect();
Expand All @@ -246,6 +436,21 @@ mod tests {
assert!(!is_hash_native_name("Foo"));
}

#[test]
fn builtin_events_keep_their_side() {
let side = |name| builtin_event(name).map(|event| event.side);
assert_eq!(side("onResourceStop"), Some(Side::Shared));
assert_eq!(side("onClientResourceStart"), Some(Side::Client));
assert_eq!(side("playerDropped"), Some(Side::Server));
assert_eq!(side("rconCommand"), Some(Side::Server));
for name in ["explosionEvent", "fireEvent", "serverEntityCreated", "onPedDeath", "stopNetworkSyncedSceneEvent"]
{
assert_eq!(side(name), Some(Side::Server), "{name}");
}
assert_eq!(side("setModel"), None, "the fields of other classes are no events");
assert_eq!(builtin_events().len(), 46);
}

#[cfg(feature = "docs")]
#[test]
fn docs_are_unescaped() {
Expand Down
Loading
Loading