Repository navigation
feat: read LuaLS type files for exports and library globals - #15
Merged
ChatDisabled merged 14 commits intoOct 1, 2026
Merged
Conversation
Type files written for LuaLS declare a function field once per way it
can be called, such as an export that takes other parameters on the
server:
---@Class PhoneExports
---@field IsInCall fun(self: PhoneExports): boolean # client-side
---@field IsInCall fun(self: PhoneExports, source: number): boolean, number? # server-side
Both fields were kept and every lookup used the first, so server code
got the client's parameters and return values. A function field that
repeats the name of an unscoped one now becomes an overload of it, the
way LuaLS reads it, and a call picks the signature its arguments fit:
`phone:IsInCall(source)` returns `boolean, number?`. Hovers show the
descriptions of both lines.
A repeated field marked `(server)` or `(client)` gives its signature
that side. Fields scoped to a side themselves stay apart as before, and
so do fields that are not functions.
…r it
Type files written for LuaLS describe the exports of a resource by
typing its `exports` entry:
---@type PhoneExports
exports["phone"] = {}
`exports["phone"]` only listed the exports the resource registers with
`exports('Name', fn)` in the workspace, so the exports of a resource
that is escrowed or not part of the workspace had no hover, completion
or type, even with such a file in the `library` setting.
`exports["res"]` and `exports.res` now take the members of a type
declared this way in any indexed file, followed by the exports the
resource registers. Where both name an export, the declared type wins.
Declarations are found across resources, as registered exports are,
so a library folder without an fxmanifest.lua works too. `exports['`
and resource-name arguments such as `GetResourceState('` also complete
the resources these declarations name.
`fivem/unknown-export` still goes by what a readable resource
registers, since a declaration does not make an export exist at
runtime.
Files that belong to no resource, such as a folder of type definitions made for LuaLS in the `library` setting, only shared their globals with each other. Their classes and aliases worked everywhere, but a resource did not see the globals they declare: `MySQL.update` from such a folder had no type, and `undefined-global` reported the names they define. A file outside any resource now declares its globals for every resource when it comes from a `library` folder, the way LuaLS reads its library, or when it is a workspace file with `---@meta` above its first statement. Other workspace files outside resources, such as the runtime scripts in a server's `artifacts` folder, keep their globals to themselves. `fivem/import-not-declared` still reports a library global like `MySQL` in a resource whose manifest lacks the import that defines it, since a type definition does not load it at runtime.
ChatDisabled
force-pushed
the
feat/declared-exports
branch
from
October 1, 2026 02:43
8e28ee5 to
f98729a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Three commits that let the server read type files written for LuaLS from the
librarysetting. Such a folder only contributed its classes and aliases: the types it declares for exports, the extra signatures of repeated@fields and the globals it defines were ignored.Types declared for exports
Type files describe the exports of a resource by typing its
exportsentry:exports["phone"]only listed the exports the resource registers withexports('Name', fn), so the exports of a resource that is escrowed or not in the workspace had no hover, completion or type.exports["res"]andexports.resnow take the members of the declared type, followed by the registered exports.---@meta.exports['and resource-name arguments such asGetResourceState('also list the resources these declarations name.fivem/unknown-export: unchanged. It still goes by what a readable resource registers, since a declaration doesn't make an export exist at runtime.Repeated function
@fieldsType files declare a function field once per way it can be called, such as an export that takes other parameters on the server:
Every lookup used the first field, so server code got the client's parameters and return values. A function field that repeats the name of an unscoped one is now an overload of it, the way LuaLS reads it, and a call picks the signature its arguments fit:
exports.phone:IsInCall(source)returnsboolean, number?.(server)or(client)gives its signature that side. Fields that are themselves scoped to a side stay separate, as before.Globals of definition files outside resources
A file that belongs to no resource only shared its globals with other such files. With a folder of LuaLS definitions in
library, a resource didn't see the globals it declares:MySQL.updatehad no type, andundefined-globalreported the names it defines. Such a file now declares its globals for every resource when:libraryfolder, the way LuaLS reads its library, or---@metaabove its first statement.Other workspace files outside resources keep their globals to themselves. Opening a whole server folder indexes
artifacts/citizen/scripting/lua/scheduler.lua, which definesCreateThread,Waitandexportswithout types.fivem/import-not-declared: still reports a library global likeMySQLwhen the manifest lacks the import that defines it, since a type file doesn't load it at runtime.libraryfolder that is also part of the workspace is indexed as workspace files first, so its files need---@meta.Checks
cargo fmt --all -- --checkandcargo clippy --workspace --all-targets --locked: clean.cargo test --workspace --locked: passes at the head of the branch, and each commit passes on its own.declared_types_describe_the_exports_of_a_resourceanddefinition_files_outside_resources_declare_globals_for_every_resource, on adeclared_exportsfixture with a workspace and alibraryfolder, and a parser testrepeated_function_fields_become_signatures.library.exports["lb-phone"]andexports["lb-tablet"]against their LuaLS type files, with a release build of the server over stdio.🤖 Generated with Claude Code