Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0bda3b8
feat: add low-memory FiveM Lua language server
ChatDisabled Sep 19, 2026
027e7e8
fix(index): resolve tables built with setmetatable, _ENV aliases and …
ChatDisabled Sep 19, 2026
1ac8b53
docs: add readme with measured comparison, benchmark scripts and ci
ChatDisabled Sep 19, 2026
b38df9c
fix(diagnostics): skip global checks in mapmanager map files
ChatDisabled Sep 19, 2026
767a9cc
fix(server): isolate panics per request and add a stress script
ChatDisabled Sep 19, 2026
6c84bf7
feat: lint the whole workspace, follow event direction and complete o…
ChatDisabled Sep 20, 2026
caaa586
feat: add formatting, cross-file diagnostics, field references and ne…
ChatDisabled Sep 20, 2026
ca2cc71
docs: describe formatting, field references and project knowledge com…
ChatDisabled Sep 20, 2026
e1270fc
fix: ignore escrowed files opened in the editor and cap problems per …
ChatDisabled Sep 20, 2026
d9c6faf
feat(hover): show table overviews with values and scope tables to the…
ChatDisabled Sep 20, 2026
3cc2119
feat(diagnostics): honour server.cfg start order and escrowed resources
ChatDisabled Sep 21, 2026
5922ce9
feat(diagnostics): pass installed resources to the linter and test ex…
ChatDisabled Sep 21, 2026
d45b692
fix(diagnostics): treat escrowed, JS and dynamic exporters as opaque
ChatDisabled Sep 21, 2026
ac4c5b8
feat(completion): offer natives for the side a guarded branch runs on
ChatDisabled Sep 21, 2026
515745c
feat(completion): rank snippets first and make onCache always available
ChatDisabled Sep 21, 2026
999da8f
feat: show handler parameters for event calls and type onCache callbacks
ChatDisabled Sep 21, 2026
d913cf8
feat(types): keep native handle names and resolve require 'glm'
ChatDisabled Sep 21, 2026
8076f32
Prepare 1.0.0 release and repository documentation
ChatDisabled Sep 22, 2026
cfd947e
Document published release downloads
ChatDisabled Sep 22, 2026
3a53438
Release 1.0.1 with corrected linter action failure handling
ChatDisabled Sep 22, 2026
1076a48
Fix event completion across colons for 1.0.2
ChatDisabled Sep 22, 2026
cd0d01f
docs: replace CHANGELOG.md with generated release notes
ChatDisabled Sep 23, 2026
78cb6b2
perf: avoid reindexing clean documents
Scorpion7162 Sep 24, 2026
989c5bb
perf: scope diagnostics to changed resources
Scorpion7162 Sep 24, 2026
aa55f1e
fix: preserve diagnostics publication behavior
Scorpion7162 Sep 24, 2026
c69c6b5
fix: preserve pending diagnostic updates
Scorpion7162 Sep 24, 2026
d5500fd
perf(server): reindex each edited document once
ChatDisabled Sep 24, 2026
ee2004a
fix(types): stop type lists at tokens that cannot start a type
ChatDisabled Sep 25, 2026
9ab8106
feat(server): reload lint settings when .luarc.json or .emmyrc.json c…
hakanesnn Sep 24, 2026
f9db4de
feat(server): log which luarc files the lint fallback used or skipped
ChatDisabled Sep 25, 2026
0b4c0b0
build(release): bump version to 1.0.3
ChatDisabled Sep 25, 2026
c7dfa50
feat: add hover and go to definition for types named in annotations
loaf-scripts Sep 25, 2026
8e714b8
fix(luacats): skip attributes before class and enum names
ChatDisabled Sep 25, 2026
15be6f5
fix(indexer): type (key) enums as the union of their keys
ChatDisabled Sep 25, 2026
66406a1
feat(luacats): find type names in @operator annotations
ChatDisabled Sep 25, 2026
805ee42
feat(luacats): find type names in @see annotations
ChatDisabled Sep 25, 2026
ab23ba0
refactor(luacats): share alias member parsing between doc parsers
ChatDisabled Sep 25, 2026
473cd9e
fix(workspace): keep excluded files out of the index and diagnostics
ChatDisabled Sep 25, 2026
c2532fd
feat(diagnostics): hide findings for ignore_diagnostics paths
ChatDisabled Sep 25, 2026
d363ebe
feat(types): infer loop, index and generic types more precisely
loaf-scripts Sep 25, 2026
bb29458
feat(types): read global array parts, pick overloads, index unions
loaf-scripts Sep 25, 2026
0e3719b
fix(infer): let nil fill optional parameters when matching overloads
ChatDisabled Sep 25, 2026
c8c34f4
fix(luacats): match overloads that list self like fun(self) fields
ChatDisabled Sep 25, 2026
abe89af
fix(infer): prefer overloads that fit without the declared vararg
ChatDisabled Sep 25, 2026
c0d0352
perf(infer): infer call arguments once and only where generics bind
ChatDisabled Sep 25, 2026
c05b6b2
fix(infer): keep table array parts apart from other keys
ChatDisabled Sep 25, 2026
bede0ab
fix(hover): show only integer-keyed tables as arrays
ChatDisabled Sep 25, 2026
09fbfde
fix(infer): keep known union parts when a generic is unbound
ChatDisabled Sep 25, 2026
1480087
fix(infer): substitute generics inside table shapes
ChatDisabled Sep 25, 2026
70f78e2
fix(infer): index top-level arrays with optional or untyped keys
ChatDisabled Sep 25, 2026
ec5b7dc
feat(lua-ls): expand FiveM resource and assistant tooling
ChatDisabled Sep 26, 2026
450c6f3
fix(workspace): record the resource that registers each event handler
ChatDisabled Sep 26, 2026
123a621
fix(assistant): keep locale checks when only supporting files were sk…
ChatDisabled Sep 26, 2026
11d4e05
feat(cli): print usage for --help
ChatDisabled Sep 26, 2026
d9a6ae5
feat(workspace): follow imports configured in qbxlint.toml
loaf-scripts Sep 27, 2026
3957972
fix(workspace): relink imports when open files enter the index
ChatDisabled Sep 30, 2026
d2d7cfb
chore(history): import qbx-lua-ls with its full commit history
ChatDisabled Sep 30, 2026
11a207d
chore(workspace): unify the linter and language server
ChatDisabled Sep 30, 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 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* text=auto eol=lf
*.tsv text eol=lf
*.lua text eol=lf
**/escrowed.lua binary
**/encrypted.lua binary
47 changes: 29 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,42 @@ jobs:
- run: rustup default stable
- if: contains(matrix.target, 'musl')
run: sudo apt-get update && sudo apt-get install -y musl-tools
- run: cargo build --release --locked -p qbx_lint --target ${{ matrix.target }}
# Build separately to keep server-only documentation out of the CLI.
- run: cargo build --profile lint-release --locked -p qbx_lint --target ${{ matrix.target }}
- run: cargo build --release --locked -p qbx_lua_ls --target ${{ matrix.target }}
- name: Check version and package
shell: bash
run: |
set -euo pipefail
cd target/${{ matrix.target }}/release
binary="qbx-lint"
if [ "${{ runner.os }}" = "Windows" ]; then binary="${binary}.exe"; fi
actual="$(./"${binary}" --version)"
expected="qbx-lint ${GITHUB_REF_NAME#v}"
if [ "${actual}" != "${expected}" ]; then
echo "Tag ${GITHUB_REF_NAME} does not match built version: ${actual}" >&2
exit 1
fi
cp ../../../LICENSE LICENSE
name="qbx-lint-${{ matrix.target }}"
if [ "${{ matrix.archive }}" = "zip" ]; then
7z a "../../../${name}.zip" "${binary}" LICENSE
else
tar -czf "../../../${name}.tar.gz" "${binary}" LICENSE
fi
root="$PWD"
for tool in qbx-lint qbx-lua-ls; do
profile=release
if [ "$tool" = qbx-lint ]; then profile=lint-release; fi
(
cd "target/${{ matrix.target }}/${profile}"
binary="$tool"
if [ "${{ runner.os }}" = "Windows" ]; then binary="${binary}.exe"; fi
actual="$(./"${binary}" --version)"
expected="$tool ${GITHUB_REF_NAME#v}"
if [ "$actual" != "$expected" ]; then
echo "Tag ${GITHUB_REF_NAME} does not match built version: ${actual}" >&2
exit 1
fi
cp "$root/LICENSE" LICENSE
name="$tool-${{ matrix.target }}"
if [ "${{ matrix.archive }}" = "zip" ]; then
7z a "$root/${name}.zip" "$binary" LICENSE
else
tar -czf "$root/${name}.tar.gz" "$binary" LICENSE
fi
)
done
- uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.target }}
path: qbx-lint-${{ matrix.target }}.${{ matrix.archive }}
path: |
qbx-lint-${{ matrix.target }}.${{ matrix.archive }}
qbx-lua-ls-${{ matrix.target }}.${{ matrix.archive }}
if-no-files-found: error

publish:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
Thumbs.db
/qbx-lint-*.zip
/qbx-lint-*.tar.gz
/qbx-lua-ls-*.zip
/qbx-lua-ls-*.tar.gz
17 changes: 14 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,22 @@ cargo run --locked -p qbx_lint -- fmt --check path/to/resource
| `qbx_fivem_data` | Native data, runtime stubs, and known manifest imports. |
| `qbx_lua_analysis` | Scopes, resource models, rules, configuration, and suppressions. |
| `qbx_lint` | Command-line interface and report formats. |
| `qbx_lua_ls` | Language server, editor features, and LSP protocol. |
| `xtask` | Native data generation. |

The [language server](https://github.com/Qbox-project/qbx-lua-ls) uses sibling path dependencies
on these crates. When changing a shared API, keep the two repositories side by side and run the
language server's tests too.
The [language server](crates/qbx_lua_ls) shares the workspace's parser, formatter, analysis and
FiveM data crates. `cargo test --workspace --locked` checks both tools; shared API changes belong
in the same pull request. See its [contribution guide](crates/qbx_lua_ls/CONTRIBUTING.md) for LSP
checks and manual probes. Editor adapters live in [qbx-editor](https://github.com/Qbox-project/qbx-editor).

Release builds use separate Cargo invocations so the linter does not enable the server's `docs`
features. The server uses the `release` profile with panic unwinding for request recovery;
the CLI keeps its abort-on-panic behavior in `lint-release`:

```sh
cargo build --profile lint-release --locked -p qbx_lint
cargo build --release --locked -p qbx_lua_ls
```

## Tests and fixtures

Expand Down
71 changes: 71 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/qbx_lua_analysis",
"crates/qbx_fivem_data",
"crates/qbx_lint",
"crates/qbx_lua_ls",
"xtask",
]
default-members = [
Expand All @@ -14,6 +15,7 @@ default-members = [
"crates/qbx_lua_analysis",
"crates/qbx_fivem_data",
"crates/qbx_lint",
"crates/qbx_lua_ls",
]

[workspace.package]
Expand All @@ -35,4 +37,8 @@ opt-level = 3
lto = "fat"
codegen-units = 1
strip = true

# LSP requests recover from panics, so the server must keep unwinding enabled.
[profile.lint-release]
inherits = "release"
panic = "abort"
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ A Lua linter and formatter for FiveM resources. It parses CfxLua syntax, reads r
and checks code against bundled native definitions, runtime globals, and client/server context.

It reports Lua mistakes, missing manifest imports, event and export mismatches, and common Qbox
patterns. The parser and analysis crates are also used by
[qbx-lua-ls](https://github.com/Qbox-project/qbx-lua-ls), the server behind the
patterns. The same workspace contains the parser, formatter, analysis crates and
[qbx-lua-ls](crates/qbx_lua_ls), the server behind the
[Qbox Lua editor integrations](https://github.com/Qbox-project/qbx-editor).

Shared analysis and language-server changes can be developed and tested in one pull request.
See the [language-server guide](crates/qbx_lua_ls/README.md) for features and setup, and the
[migration notes](docs/repository-migration.md) for Git history and release compatibility.

## Install

Build from a checkout with a stable Rust toolchain and Cargo:

```sh
git clone https://github.com/Qbox-project/qbx-lint.git
cd qbx-lint
cargo install --path crates/qbx_lint --locked
cargo install --path crates/qbx_lint --locked --profile lint-release
qbx-lint --version
```

Cargo installs the executable into its `bin` directory, which must be on your `PATH`.
For a local build without installation, run `cargo build --release --locked -p qbx_lint`.
The executable is then in `target/release` (`qbx-lint.exe` on Windows).
For a local build without installation, run `cargo build --profile lint-release --locked -p qbx_lint`.
The executable is then in `target/lint-release` (`qbx-lint.exe` on Windows).

Download the archive for your platform from the
[releases page](https://github.com/Qbox-project/qbx-lint/releases), extract it, and put the
Expand Down
73 changes: 73 additions & 0 deletions crates/qbx_lua_ls/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Contributing

## Set up

Install stable Rust and clone [qbx-lint](https://github.com/Qbox-project/qbx-lint). The language
server is a member of the same Cargo workspace as the parser, formatter, analysis and FiveM data
crates:

```text
qbx-lint/
Cargo.toml
crates/
qbx_lua_ls/
qbx_lua_analysis/
...
```

From the workspace root, build with `cargo build --locked -p qbx_lua_ls`. If a change needs shared
parser or lint behavior, include those crate changes in the same pull request.

## Check changes

Run these commands from the workspace root before submitting a pull request:

```sh
cargo fmt --all --check
cargo clippy --workspace --locked --all-targets -- -D warnings
cargo test --workspace --locked
```

Use `cargo fmt` to apply formatting. Add a regression test when fixing a bug, using a small Lua
example and the manifest needed to reproduce it. The tests in `tests/lsp.rs` run the server
through an in-memory LSP connection. `tests/workspace_refresh.rs` covers event-side behavior
and index refreshes with temporary files.

Check both minimal LSP clients and clients that advertise the relevant capability when changing
protocol behavior. Editor integration code belongs in
[qbx-editor](https://github.com/Qbox-project/qbx-editor).

For manual inspection, build the executable and use the probe script:

```sh
cargo build --release --locked -p qbx_lua_ls
node crates/qbx_lua_ls/scripts/probe.mjs <workspace> <virtual-file> <server-executable> < snippet.lua
```

`probe.mjs` requires Node.js. It opens the supplied text as a document and prints completions
for lines marked with `--^` and hovers for `--?<column>` markers. Use the `.exe` executable on
Windows. Run the command in a shell that supports input redirection.

## Code layout

| Location | Purpose |
| --- | --- |
| `src/types.rs`, `src/luacats.rs` | Type representation and LuaCATS annotations. |
| `src/indexer.rs`, `src/index.rs` | File summaries, symbols and visibility between resources. |
| `src/infer.rs` | Type inference used by editor features. |
| `src/workspace.rs` | Resource discovery, manifests, dependencies and index refreshes. |
| `src/features/` | LSP feature implementations. |
| `src/server.rs` | Protocol dispatch, settings and diagnostics publication. |

Closed files retain summaries rather than full syntax trees. Reference searches may parse them
again. Keep that distinction in mind when adding data to the index or handling unsaved text.

## Issues and pull requests

For a bug report, include the server version, operating system, editor/LSP client, relevant
settings, expected behavior and a small Lua/manifest example. For crashes, include server logs
and the last action that triggered the problem.

Describe the behavior changed by a pull request and list the checks you ran. State any editor
or platform behavior you could not test. Update the docs when the user-facing
behavior changes. Release notes are generated from commit messages, so write them as [Conventional Commits](https://www.conventionalcommits.org).
25 changes: 25 additions & 0 deletions crates/qbx_lua_ls/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "qbx_lua_ls"
description = "Language server for FiveM Lua"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true

[[bin]]
name = "qbx-lua-ls"
path = "src/main.rs"

[dependencies]
qbx_lua_syntax.workspace = true
qbx_lua_fmt.workspace = true
qbx_lua_analysis = { workspace = true, features = ["docs"] }
qbx_fivem_data = { workspace = true, features = ["docs"] }
lsp-server = "0.7"
lsp-types = "0.95"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
crossbeam-channel = "0.5"
rustc-hash.workspace = true
smol_str.workspace = true
walkdir = "2"
Loading
Loading