9.5 KiB
9.5 KiB
paths
| paths | |
|---|---|
|
Lua Hooks
This file extends 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):
-- .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
# 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)
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
# 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
// .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:
---@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
#!/bin/sh
# .git/hooks/pre-commit
set -e
echo "=== Luacheck ==="
luacheck . --no-color
echo "=== StyLua ==="
stylua --check .
echo "=== All checks passed ==="
Makefile Targets
.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)
# .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:
- Run
luacheck <file>on the modified file - Run
stylua --check <file>on the modified file - 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 |