Skip to content

lua-language-server type rules, a strict mode, flow narrowing and FiveM data updates - #20

Merged
ChatDisabled merged 193 commits into
Qbox-project:mainfrom
loaf-scripts:main
Oct 21, 2026
Merged

ChatDisabled merged 193 commits into
Qbox-project:mainfrom
loaf-scripts:main

Conversation

@loaf-scripts

@loaf-scripts loaf-scripts commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Changes

This brings the type checks of qbx-lua-ls close to lua-language-server 3.19.1. It also adds an opt-in strict mode that reports what TypeScript's strict mode would. And it fixes the indexing and inference gaps that showed up while comparing the two on real FiveM resources.

It is 155 commits. Each is one change with its own tests and docs, so it can be reviewed commit by commit, and the release notes read from the commit messages.

New rules

Most new rules default to warning, so projects will see new findings after upgrading. The exceptions are trailing-space, redundant-return and unused-vararg, which default to hint, and the missing-doc rules, which are off. Rules that need types run in qbx-lua-ls, and through qbx-lua-ls --check outside an editor.

  • Types:
    • param-type-mismatch, need-check-nil, cast-type-mismatch, cast-local-type
    • inject-field, redundant-return-value, discard-returns, invisible
    • close-non-object, circle-doc-class, unknown-operator, unknown-cast-variable
  • Code:
    • redundant-parameter, count-down-loop, duplicate-set-field
    • newline-call, newfield-call, ambiguity-1, global-in-nil-env
    • redundant-return, unused-vararg, trailing-space
  • Doc comments:
    • undefined-doc-param, duplicate-doc-alias, duplicate-doc-field, duplicate-doc-param, doc-field-no-class, unknown-diag-code
    • the opt-in missing-global-doc, missing-local-export-doc and incomplete-signature-doc

Existing rules

  • missing-fields: checks tables typed as a union of classes and shapes, or as a shape. It also checks the tables that arrays, maps and tuples hold. A (partial) class requires only its own fields, and ---@meta files are skipped.
  • undefined-field and inject-field: report fields read from, or set on, values whose type lacks them.
  • assign-type-mismatch and return-type-mismatch: check each type of a union, the entries stored in declared tables, what table.insert adds, and typed locals that nothing assigns.
  • unreachable-code, deprecated, unused-local, unused-function, redefined-local, missing-parameter: cover the cases lua-language-server reports and these missed.
  • native-wrong-side: reports names of one side used in shared scripts, and server-only Lua libraries used on the client.
  • no-unknown: reports values of unknown type stored where a type is declared.
  • fivem/event-missing-arguments: leaves out the handler parameters that its doc comment marks optional.
  • lowercase-global, implicit-global and unused-argument: leave ---@meta stubs alone.

Strict mode

strict = true in qbxlint.toml turns it on. It also works per folder through [[overrides]], or through the editor's diagnostics.strict. The type rules then also report what TypeScript's strict mode does, such as n + 1 for a number?, or a string? passed for a string.

It is off by default. Without it, the rules aim to report what lua-language-server 3.19.1 reports on the same code. The index, hover and completion don't depend on it.

Inference

  • Narrowing:
    • type() and math.type() checks, and comparisons with literals and with other locals;
    • elseif chains, and locals that are assigned again;
    • fields of locals and globals, and ---@cast lines;
    • aliased conditions, as if ok after local ok = x and f(x);
    • and/or sides whose type is never false.
  • Classes and generics:
    • classes built with setmetatable and __index;
    • generic classes and aliases read through their type arguments;
    • table types named as class parents, and @operator lines;
    • backtick generics, and { [K]: V } binding generics as table<K, V> does.
  • Functions:
    • A function has the signatures of every definition the code sees.
    • ---@extend Name fun(...) adds signatures from other files.
    • A function written as a value takes the types that its table constructor, field, @return, or/and side or statement declares.
    • assert, tonumber and ...T returns are typed, and so are calls of functions that return nothing.

Language server

  • Go to type definition, and go to implementation.
  • Completion of enum members, and the descriptions of ---| values in completion and hover.
  • Hovers write out aliases and table types. They show more or less at the verbosity level that lua-language-server's VS Code client asks for.
  • qbx-lua-ls --check [<dir>...] reports what the editor reports, from the command line.
  • A sql type and the qbx/sqlFunctions request, so editors can highlight queries.
  • Indexing:
    • closed files are indexed again when the globals they read change;
    • the index no longer depends on the order files were indexed in;
    • files nobody has open are reported without waiting for a client message;
    • symlinked folders are followed as FiveM does.

FiveM data

  • Natives: refreshed from static.cfx.re in their own commit, feat(natives): update the natives and return their pointers as the Lua wrappers do, so that data diff can be reviewed apart. Natives return their pointers after the result, as the Lua wrappers do. The functions they call back and the tables they return are typed.
  • Events: cfx_events.lua declares the events FiveM itself triggers. They're used for completion, hover, handler parameter types and fivem/event-wrong-side.
  • Runtime stubs: these match the CfxLua runtime, including CreateThread, json.decode, msgpack.unpack, table.*, RegisterNetEvent, load, string.find and os.date('*t'). The server-only Lua libraries are split into their own stub.

LuaCATS

  • New syntax:
    • ---@extend;
    • async fun(...);
    • ---| lines that continue @param, @return, @type and @field;
    • [K]?: V.
  • self in doc types resolves to the class of the function's table.
  • Comments and directives inside doc blocks no longer end them.

docs/rules.md, docs/reference.md and the language server's README and protocol docs describe each change.

The editor side is in the qbx-editor PR Qbox-project/qbx-editor#7: the strict setting, hover levels, SQL highlighting and ---@extend highlighting. Its CI builds this repository's main, so this PR should merge first.

Checks

  • On the branch tip, these pass on Windows:
    • cargo fmt --all --check
    • cargo clippy --workspace --all-targets --locked -- -D warnings
    • cargo test --workspace --locked
    • cargo test --locked -p qbx_fivem_data --features docs
  • The release builds succeed: cargo build --release --locked -p qbx_lua_ls and cargo build --profile lint-release --locked -p qbx_lint.
  • Rule changes come with reported and valid cases in crates/qbx_lua_ls/tests and crates/qbx_lua_analysis/tests.
  • The changes were measured on about 9,500 Lua files from QBox, QBCore, ESX and other FiveM servers and resources, in default and strict mode. I reviewed what each change added and removed, and compared default mode with lua-language-server 3.19.1 on the same code.
  • Not run: the Linux CI job.

🤖 Generated with Claude Code

@ChatDisabled
ChatDisabled merged commit fd82d54 into Qbox-project:main Oct 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants