v1.4.9: Validation fixes, Profiler fix, Quest link duplicate fix, Ascension fixes
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.lua"
|
||||
---
|
||||
# Lua Hooks
|
||||
|
||||
> This file extends [common/hooks.md](../common/hooks.md) with Lua specific content.
|
||||
|
||||
## Tool Chain Summary
|
||||
|
||||
| Tool | Purpose | Install | Run |
|
||||
|------|---------|---------|-----|
|
||||
| **Luacheck** | Static analysis (undefined globals, unused vars, shadowed locals) | `luarocks install luacheck` | `luacheck .` |
|
||||
| **StyLua** | Opinionated code formatter (Rust-based, fast) | `cargo install stylua` | `stylua .` |
|
||||
| **Luacov** | Line coverage reporting | `luarocks install luacov` | `busted --coverage && luacov` |
|
||||
| **lua-language-server** | IDE diagnostics, type checking, completion | VS Code extension | Automatic |
|
||||
|
||||
## Static Analysis: Luacheck
|
||||
|
||||
Luacheck detects:
|
||||
- Undefined global variables (critical for preventing taint)
|
||||
- Unused local variables and function arguments
|
||||
- Shadowed local variables
|
||||
- Unreachable code after `return`
|
||||
- Unused values assigned to variables
|
||||
|
||||
### Configuration (`.luacheckrc`)
|
||||
|
||||
Place at project root. Be exhaustive with known globals to eliminate false positives.
|
||||
|
||||
**Version targeting**: Set `std` based on your runtime. Use `"lua50"` for 5.0, `"lua51"` for 5.1, or `"none"` for maximum strictness (recommended for WoW addons where you must declare every global):
|
||||
|
||||
```lua
|
||||
-- .luacheckrc
|
||||
-- For Lua 5.0 projects: use "lua50" to allow table.getn, table.setn, etc.
|
||||
-- For Lua 5.1 projects: use "lua51"
|
||||
-- For WoW addons: use "none" (strictest; manually declare all globals)
|
||||
std = "none"
|
||||
max_line_length = 120
|
||||
cache = true -- Speed up repeated runs
|
||||
|
||||
-- Allowed globals — explicitly list every WoW API function used
|
||||
globals = {
|
||||
-- Core addon system
|
||||
"QuestieLoader", "Questie", "QuestieDB",
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
-- Lua builtins (read-only)
|
||||
"select", "unpack", "pcall", "xpcall", "error", "assert",
|
||||
"type", "tostring", "tonumber", "rawget", "rawset",
|
||||
"setmetatable", "getmetatable", "next", "pairs", "ipairs",
|
||||
"coroutine", "string", "table", "math", "bit",
|
||||
|
||||
-- WoW Frame API
|
||||
"CreateFrame", "UIParent",
|
||||
|
||||
-- WoW Timer API
|
||||
"C_Timer",
|
||||
|
||||
-- WoW Map API
|
||||
"C_Map", "C_QuestLog",
|
||||
|
||||
-- WoW Unit API
|
||||
"UnitGUID", "UnitName", "UnitLevel", "UnitFactionGroup",
|
||||
"UnitClass", "UnitRace", "GetRealmName",
|
||||
|
||||
-- WoW Addon API
|
||||
"IsAddOnLoaded", "GetAddOnInfo", "GetNumAddOns",
|
||||
"GetAddOnMetadata",
|
||||
|
||||
-- WoW Combat API
|
||||
"InCombatLockdown",
|
||||
|
||||
-- WoW Misc
|
||||
"Enum", "GetTime", "GetLocale", "GetBuildInfo",
|
||||
"SlashCmdList", "SLASH_QUESTIE1",
|
||||
"hooksecurefunc", "debugstack", "geterrorhandler",
|
||||
"print", "format", "wipe", "strsplit", "strtrim",
|
||||
"tinsert", "tremove",
|
||||
|
||||
-- Lua 5.0-specific globals (add if targeting 5.0)
|
||||
-- "loadlib", -- 5.0 global; moved to package.loadlib in 5.1+
|
||||
-- Note: table.getn, table.setn, table.foreach, table.foreachi,
|
||||
-- math.mod, and string.gfind are methods on their parent tables.
|
||||
-- Luacheck already allows them via the "table", "math", and "string"
|
||||
-- entries above. Use std = "lua50" if you need full 5.0 stdlib.
|
||||
|
||||
-- SavedVariables (read-only access is acceptable)
|
||||
"QuestieSV",
|
||||
}
|
||||
|
||||
-- Per-directory overrides
|
||||
files["Database/Data/**"] = {
|
||||
max_line_length = false, -- Data files have long lines
|
||||
ignore = { "631" }, -- Allow line length variance
|
||||
}
|
||||
|
||||
files["tests/**"] = {
|
||||
std = "+busted", -- Add Busted globals (describe, it, assert, etc.)
|
||||
globals = {
|
||||
"_G", -- Tests may manipulate _G for mocking
|
||||
},
|
||||
}
|
||||
|
||||
files["Localization/**"] = {
|
||||
max_line_length = false, -- Translation strings can be long
|
||||
}
|
||||
|
||||
-- Warnings to suppress project-wide
|
||||
ignore = {
|
||||
"212", -- Unused argument (common in callbacks: function(self, event, ...))
|
||||
"213", -- Unused loop variable (for _ in pairs)
|
||||
}
|
||||
```
|
||||
|
||||
### Running Luacheck
|
||||
|
||||
```bash
|
||||
# Full project lint
|
||||
luacheck .
|
||||
|
||||
# Single file with column info
|
||||
luacheck Database/QuestieDB.lua --codes --ranges
|
||||
|
||||
# CI mode: no color, non-zero exit on warnings
|
||||
luacheck . --no-color --formatter plain
|
||||
|
||||
# Show only errors (ignore warnings)
|
||||
luacheck . --only 0
|
||||
|
||||
# List all globals used (audit for taint)
|
||||
luacheck . --globals --no-unused --no-redefined
|
||||
```
|
||||
|
||||
### Common Warning Codes
|
||||
|
||||
| Code | Meaning | Fix |
|
||||
|------|---------|-----|
|
||||
| 111 | Setting undefined global | Add `local` or add to `globals` list |
|
||||
| 112 | Mutating undefined global | Same as 111 |
|
||||
| 113 | Accessing undefined global | Add to `read_globals` or add `local` |
|
||||
| 211 | Unused local variable | Remove or prefix with `_` |
|
||||
| 212 | Unused argument | Prefix with `_` or add to `ignore` |
|
||||
| 311 | Unused value | Remove the assignment |
|
||||
| 411 | Redefining local variable | Rename or restructure |
|
||||
| 421 | Shadowing local variable | Rename inner variable |
|
||||
| 542 | Empty if branch | Add logic or use guard pattern |
|
||||
|
||||
## Formatting: StyLua
|
||||
|
||||
### Configuration (`stylua.toml`)
|
||||
|
||||
```toml
|
||||
column_width = 120
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 4
|
||||
quote_style = "AutoPreferDouble"
|
||||
call_parentheses = "Always"
|
||||
collapse_simple_statement = "Never"
|
||||
|
||||
[sort_requires]
|
||||
enabled = false # Lua module loading order matters
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Check formatting without modifying (CI)
|
||||
stylua --check .
|
||||
|
||||
# Auto-format all Lua files
|
||||
stylua .
|
||||
|
||||
# Format a single file
|
||||
stylua Database/QuestieDB.lua
|
||||
|
||||
# Preview changes (diff mode)
|
||||
stylua --check --output-format=diff .
|
||||
```
|
||||
|
||||
## Language Server: lua-language-server (Sumneko)
|
||||
|
||||
### VS Code Configuration
|
||||
|
||||
```json
|
||||
// .vscode/settings.json
|
||||
{
|
||||
// Set to "Lua 5.0" for 5.0 projects, "Lua 5.1" for WoW, etc.
|
||||
"Lua.runtime.version": "Lua 5.1",
|
||||
"Lua.diagnostics.globals": [
|
||||
"Questie", "QuestieLoader", "QuestieDB",
|
||||
"CreateFrame", "C_Timer", "C_Map", "Enum",
|
||||
"GetTime", "IsAddOnLoaded", "InCombatLockdown",
|
||||
"hooksecurefunc", "debugstack", "wipe",
|
||||
"print", "format", "strsplit"
|
||||
],
|
||||
"Lua.workspace.library": [
|
||||
// Path to WoW API type definitions if available
|
||||
],
|
||||
"Lua.workspace.ignoreDir": [
|
||||
"Database/Data",
|
||||
".release"
|
||||
],
|
||||
"Lua.diagnostics.disable": [
|
||||
"lowercase-global"
|
||||
],
|
||||
"Lua.completion.callSnippet": "Replace",
|
||||
"Lua.hint.enable": true
|
||||
}
|
||||
```
|
||||
|
||||
### Type Annotations (EmmyLua / lua-language-server)
|
||||
|
||||
Use `---@` annotations to add type safety in supported IDEs:
|
||||
|
||||
```lua
|
||||
---@class QuestieDB
|
||||
---@field npcData table<number, table>
|
||||
---@field questData table<number, table>
|
||||
local QuestieDB = {}
|
||||
|
||||
---@param npcId number
|
||||
---@return table|nil npcData
|
||||
---@return string|nil errorMessage
|
||||
function QuestieDB:GetNPC(npcId)
|
||||
-- ...
|
||||
end
|
||||
```
|
||||
|
||||
## Pre-Commit Hook
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
# .git/hooks/pre-commit
|
||||
set -e
|
||||
|
||||
echo "=== Luacheck ==="
|
||||
luacheck . --no-color
|
||||
|
||||
echo "=== StyLua ==="
|
||||
stylua --check .
|
||||
|
||||
echo "=== All checks passed ==="
|
||||
```
|
||||
|
||||
## Makefile Targets
|
||||
|
||||
```makefile
|
||||
.PHONY: lint format test coverage ci
|
||||
|
||||
lint:
|
||||
luacheck . --no-color
|
||||
stylua --check .
|
||||
|
||||
format:
|
||||
stylua .
|
||||
|
||||
test:
|
||||
busted --verbose
|
||||
|
||||
coverage:
|
||||
busted --coverage
|
||||
luacov
|
||||
@awk '/^Total/ { if ($$4+0 < 80) { print "FAIL: Coverage " $$4 "% < 80%"; exit 1 } else { print "PASS: Coverage " $$4 "%"; } }' luacov.report.out
|
||||
|
||||
ci: lint test coverage
|
||||
```
|
||||
|
||||
## CI Pipeline (GitHub Actions)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/lua-ci.yml
|
||||
name: Lua CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Lua
|
||||
uses: leafo/gh-actions-lua@v10
|
||||
with:
|
||||
luaVersion: "5.1"
|
||||
|
||||
- name: Setup LuaRocks
|
||||
uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
luarocks install luacheck
|
||||
luarocks install busted
|
||||
luarocks install luacov
|
||||
|
||||
- name: Install StyLua
|
||||
run: |
|
||||
curl -L -o stylua.zip https://github.com/JohnnyMorganz/StyLua/releases/latest/download/stylua-linux-x86_64.zip
|
||||
unzip stylua.zip -d /usr/local/bin/
|
||||
chmod +x /usr/local/bin/stylua
|
||||
|
||||
- name: Lint (Luacheck)
|
||||
run: luacheck . --no-color
|
||||
|
||||
- name: Format Check (StyLua)
|
||||
run: stylua --check .
|
||||
|
||||
- name: Test
|
||||
run: busted --output=TAP --coverage
|
||||
|
||||
- name: Coverage
|
||||
run: |
|
||||
luacov
|
||||
awk '/^Total/ { if ($4+0 < 80) { print "FAIL: " $4 "%"; exit 1 } }' luacov.report.out
|
||||
```
|
||||
|
||||
## PostToolUse Hook Behavior
|
||||
|
||||
After every Lua file edit or creation, the agent SHOULD:
|
||||
|
||||
1. Run `luacheck <file>` on the modified file
|
||||
2. Run `stylua --check <file>` on the modified file
|
||||
3. Report any issues **before** proceeding to the next edit
|
||||
|
||||
This catches errors immediately rather than accumulating them across a multi-file change.
|
||||
|
||||
## WoW-Specific: Taint Detection
|
||||
|
||||
Monitor for these errors in the WoW error log — they indicate your addon is touching protected state:
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `ADDON_ACTION_BLOCKED` | Tainted code called a protected API | Remove the taint source (`loadstring`, `_G` writes) |
|
||||
| `ADDON_ACTION_FORBIDDEN` | Addon tried to call a hardware event API | Guard with `InCombatLockdown()` |
|
||||
| `Couldn't find frame` | Invalid secure template reference | Check template names in `CreateFrame` |
|
||||
|
||||
Common taint sources and their fixes:
|
||||
|
||||
| Taint Source | Fix |
|
||||
|-------------|-----|
|
||||
| `loadstring()` in addon code | Replace with function dispatch tables |
|
||||
| `_G.MyAddon_Data = data` | Use `addonTable` from the TOC vararg |
|
||||
| Writing globals from `OnUpdate` | Move writes to `ADDON_LOADED` or `PLAYER_LOGIN` |
|
||||
| `rawset(_G, name, value)` | Use local module tables instead |
|
||||
| Hooking with function replacement | Use `hooksecurefunc()` (post-hook only, never overwrite) |
|
||||
| Calling restricted APIs after taint | Isolate tainted code from secure code paths |
|
||||
Reference in New Issue
Block a user