Skip to content

chore: combine the linter and language server in one workspace - #8

Merged
ChatDisabled merged 58 commits into
mainfrom
chore/unify-lua-tooling
Sep 30, 2026
Merged

ChatDisabled merged 58 commits into
mainfrom
chore/unify-lua-tooling

Conversation

@ChatDisabled

@ChatDisabled ChatDisabled commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Changes

Shared analysis changes currently require coordinating two repositories and matching release tags. Import qbx-lua-ls into crates/qbx_lua_ls and make it a member of the existing Cargo workspace, so one PR and one CI run can change and test the linter and server together.

The full-history subtree import preserves all 47 linter commits and 56 server commits from the current main branches, including their original hashes and authors. The imported server's src/, tests/ and docs/ trees match the original repository exactly. Integration changes share workspace metadata/dependencies and one lockfile, remove duplicate repository configuration, update developer documentation/scripts, and package both binaries under their existing archive names.

Release builds remain separate so server-only documentation stays out of the CLI. The server keeps panic unwinding for LSP request recovery; the CLI retains abort-on-panic through the lint-release profile. The existing root GitHub Action remains available.

Merge this PR using Create a merge commit. Do not squash or rebase it: that would discard the imported commit graph from main.

docs/repository-migration.md documents namespacing the five historical server tags under lua-ls/, publishing the first shared release, updating the editor, and archiving the original server repository after cutover. No existing tags or releases are changed by this PR.

Companion editor migration: Qbox-project/qbx-editor#3. Merge that after this PR and the first shared release.

Checks

  • cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, and cargo test --workspace --locked passed on Windows.
  • The workspace PR's GitHub CI passed on both Ubuntu and Windows.
  • Both production binaries built successfully with their respective profiles; both report 1.0.4.
  • Git reachability checks confirm every original main-branch commit remains unchanged. All five original server tag objects are retained locally under lua-ls/; their publication is a documented cutover step.
  • Actionlint passed for CI, releases and native-data maintenance. The release workflow's actual packaging script produced both Windows ZIPs from a path containing spaces, preserved the exact executable/license bytes, and rejected a mismatched version tag.
  • The companion editor changes passed TypeScript/build checks, the VS Code integration suite, seven Marketplace verification tests, and Zed formatting/Clippy/Wasm release build. A Windows VSIX was packaged, its bundled binary verified, and installed into an isolated test profile.

Linux/macOS builds remain covered by the existing release matrix; no release was published during validation.

ChatDisabled and others added 30 commits September 19, 2026 04:01
Closed files are reduced to a compact symbol summary and only open documents keep a syntax tree, which keeps the resident set around 10 MB for a handful of resources. Types come from LuaCATS annotations, table shapes, natives and manifest-aware globals; the lint rules are shared with qbx-lint so the editor and CI agree.
…lazy library members

Probing against ox_lib and qbx_core showed that their main tables were invisible: they are created through setmetatable, published with _ENV.name, extended from lazily loaded files and, for the ox_lib cache, filled through cache:set. A second index pass also settles types that depend on files indexed later.
The release profile aborted on panic, so a single bug would have killed the editor's server. Requests and notifications now run behind catch_unwind and answer with an internal error instead. scripts/stress.mjs fires every feature at random positions of every file in a workspace to find such bugs.
…x_lib cache keys

Closed files are linted one at a time when the server is idle so the Problems panel covers the workspace, and a save or close only re-lints the resource it belongs to. Event name completion now only offers handlers the call can reach, lib.onCache and an onCache snippet list the cache keys found in the indexed ox_lib source, and qbx/fileInfo tells editors which side a file runs on.
…w completions

The server now formats documents with qbx_lua_fmt, feeds the cross-file lint rules from its index (event and export arity, wrong side, security, dependencies, locale keys including unused keys reported on the JSON file), finds and renames fields and methods across related files by resolving each candidate's owner type, and completes locale keys, convars from code and .cfg files, and state bag keys.
… current resource

Hovering a table now lists its fields with types and the short literal values remembered by the index. Global lookups no longer fall back to symbols of unrelated resources, and a table the resource fills itself is no longer merged with same-named tables of imported libraries, so Config means this resource's Config.
Calling an export of an escrowed resource was reported as unknown
because only the readable Lua was considered. Resources are now opaque
when they ship a .fxap marker, contain an encrypted file, run non-Lua
scripts or register exports under computed names, and the cross-file
rules stay silent about them.

The escrow state is kept on the resource entry, which also makes a
resource with an encrypted file but no marker count, as the linter
already did. The encrypted fixture moves to its own resource so the
shop fixture keeps exercising the cross-file rules.
Inside `if IsDuplicityVersion() then` of a client or shared file the
server natives were hidden, because only the manifest side was used.
Diagnostics pick the same narrowing up through the linter.

The probe script now only records diagnostics of the probed document,
prints the resolved side, and can print messages with PROBE_VERBOSE.
A snippet shared its label with the plain function and sorted after it,
so accepting the first `CreateThread` only inserted the word. Snippets
now sort ahead, are marked as such in the list and preview their body.

CreateThread expands to the loop with Wait(0), which is what people
want nearly every time; `CreateThread once` keeps the bare form and
SetTimeout joins the list. onCache no longer needs ox_lib in the
workspace (it falls back to the usual cache keys) and is also offered
after `lib.`.
Signature help and inlay hints for TriggerServerEvent and friends only
knew the native's `...: any`, although the index already holds the
handler registered under the event name. They now show the native's
leading parameters followed by the handler's, and say where the handler
lives. lib.callback calls get the same, minus the `source` parameter
of server callbacks.

The callback of lib.onCache('key', ...) takes its parameter types from
cache.key. Native handle types (Vehicle, Ped, Hash, ...) resolve to
integer instead of whatever class a resource declared under that name.

Snippets use the event and keyword icons, preview their body without
placeholder syntax, and are listed by the new qbx/snippets request.
Mapping Vehicle, Ped, Hash and the other handle types to integer fixed
the clash with classes of the same name in ox_lib and qbx_core, but it
also turned every native signature into `vehicle: integer`. A handle is
now its own type: it prints under its name, has no members and never
looks a class up.

`require 'glm'` returns the built-in library instead of a module of the
resource, which is how ox_lib obtains it.
The shared release workflow now builds release notes from the
commits since the previous tag, so the hand-kept file is redundant.
Requests flush dirty documents before handling, then workspace publication flushed the same documents again. Remove each document from the dirty set immediately after its index update.
A dirty document triggered diagnostics for every open document. Publish changed documents and open documents in their affected resources, where shared diagnostics context can change.
Keep the dirty-document indexing optimization while retaining diagnostics publication for every open document. Cross-file diagnostics can depend on documents outside the changed resource.
Request-time indexing must not consume dirty document state before idle diagnostics publication. Keep dirty markers until publish_dirty completes so diagnostics are sent after requests.
- Requests flushed the dirty set without draining it, so every request
  in a burst and the idle publish reindexed the same documents again.
- flush_index now drains the set and marks diagnostics as pending;
  publish_dirty reads that flag instead of the set.
- Replace the test that passed without the fix with one that fails on
  both the old flush and a drain that drops publication.
…hanges

qbx-lint can take its settings from a LuaLS or EmmyLua config when
there is no qbxlint.toml, so watch those files as well and reload the
config when they change, the same way qbxlint.toml is handled.

Formatting used an empty config root to tell that no qbxlint.toml was
found and the editor's indentation should apply. A config read from
.luarc.json has a root, which would have switched formatting to
qbx-lint's defaults, so check format_configured instead.
ChatDisabled and others added 26 commits September 25, 2026 16:03
Loop variables, indexed fields and generic calls now get the types
their tables and annotations describe instead of `unknown`.

- pairs/ipairs/next: keep literal types for inline tables, read the
  array part of top-level local tables, and handle tuples, class
  fields, unions and `for k, v in next, t`. ipairs yields only the
  array part.
- Indexing: a key typed as string literals looks up each named field,
  and `list[i]` works on top-level local arrays. Table constructors
  keep their array part and every `[key]` next to named fields.
- Generics: record `@generic` names on function types and bind them
  from `table<K, V>`, `V[]` and `T?` parameters and callback returns.
  Callback parameters use the bound types, and unbound generics are
  `unknown` rather than their names.
- Hover: string literal unions and aliases no longer list the string
  library, aliases of non-table types show their definition, and a
  top-level local array shows as `string[]`.
- Index the array part and `[key]` entries of every indexed table,
  so `Config.Items = { 'a', 'b' }` loops and indexes as `string[]`.
- Keep `@overload` signatures on function types. A call uses the
  declared signature unless its argument count or value kinds rule
  it out, then the first overload that fits, for both the result
  and callback parameter types.
- Index each part of a union: `(string[]|integer[])[1]` is
  `string|integer`.
git-subtree-dir: crates/qbx_lua_ls
git-subtree-mainline: 6cdc955
git-subtree-split: 3957972
@ChatDisabled
ChatDisabled marked this pull request as ready for review September 30, 2026 02:07
@ChatDisabled
ChatDisabled merged commit 0eae73d into main Sep 30, 2026
2 checks passed
@ChatDisabled
ChatDisabled deleted the chore/unify-lua-tooling branch September 30, 2026 02:07
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.

4 participants