Stub Generation
wowlua-ls ships with precomputed type stubs for the entire WoW API — around 150,000 symbols covering functions, classes, enums, events, and global variables across retail, classic, and classic era. This page documents the data sources, pipeline, and output artifacts that make this work.
Stubs are regenerated with:
cargo run -- regenerate-stubsThe git repositories below (Ketho/vscode-wow-api and Gethe/wow-ui-source) are shallow-cloned into a persistent cache under the platform cache directory ($XDG_CACHE_HOME/~/.cache/wowlua-ls/clones/ on Linux/macOS, %LOCALAPPDATA%\wowlua-ls\clones\ on Windows). On subsequent runs the cached clones are updated in place with git fetch --depth 1 + git reset --hard rather than re-cloned from scratch, saving most of the clone time. Set the WOWLUA_LS_REFRESH_CLONES environment variable to force fresh clones.
Data sources
The pipeline integrates six major sources into a single type database.
1. Ketho/vscode-wow-api
Ketho/vscode-wow-api is shallow-cloned with its submodules (which pulls in NumyAddon/FramexmlAnnotations into Annotations/FrameXML/).
Annotations/Core/andAnnotations/FrameXML/— Pre-written LuaLS-style annotation.luafiles containing type stubs for classes, functions, and fields related to frames and widgets. These are scanned directly as Lua. Widget stubs with a---[Documentation](...)link but no@param/@returnannotations are enriched by fetching the linked wiki page and injecting parsed type annotations into the vendor files before scanning.
The submodule initialization fetches NumyAddon/FramexmlAnnotations into Annotations/FrameXML/. Ketho/BlizzardInterfaceResources data is accessed via GitHub raw URLs rather than the local clone.
2. wago.tools
wago.tools provides database exports via HTTP API.
api/builds/{product}/latest— Fetched to discover the latest build number for a given WoW product (e.g.wowfor retail). Returns JSON with aversionfield (e.g.12.0.5.67602).db2/GlobalStrings/csv?build={build}&locale=enUS— Full CSV export of theGlobalStringsDB2 table for the given build. Columns:ID,BaseTag(variable name),TagText_lang(English string value),Flags. Used to generateGlobalStrings.lua. CRLF line endings in values are normalised to LF. All valid identifier names not already covered by hand-written stubs are emitted directly.db2/GlobalColor/csv— Full CSV export of theGlobalColorDB2 table (no build filter — uses latest available data for maximum coverage). Columns:ID,LuaConstantName,Color(signed int32, packed ARGB). Used to generateGlobalColors.lua. For each entry, two globals are emitted: the color object itself (typedcolorRGBA) and a_CODEstring variant (e.g.GREEN_FONT_COLORandGREEN_FONT_COLOR_CODE). The_CODEvariants are color escape strings generated at runtime by WoW's FrameXML viaGenerateHexColorMarkup().
3. Ketho/BlizzardInterfaceResources
Ketho/BlizzardInterfaceResources is fetched via raw GitHub URLs across three branches: live, classic_era, classic.
Resources/GlobalAPI.luaandResources/FrameXML.lua— Simple name lists used to compute the classic-only API diff by identifying names present in classic branches but absent from retail/live. Also used as the source of global variable names forGlobalVariables.luaand per-API flavor bitmasks (derived from which branches contain each name).Resources/LuaEnum.lua— Parsed forLE_*legacy enum constant values (nestedEnum = { Category = { ValueName = N } }structures converted from CamelCase toLE_UPPER_SNAKEformat, cross-referenced against wow-ui-source FrameXMLLE_*references);Enum.*enum category stubs (merged with APIDocumentation enumerations); and theConstantsglobal table (sub-tables with typed number/boolean/string fields).Resources/CVars.lua— Parsed for["cvarName"] = { ... }entries to generate theCVarstring literal union type alias.
4. Gethe/wow-ui-source
Gethe/wow-ui-source is shallow-cloned across three branches: live, classic_era, classic.
Interface/AddOns/**/*.xml— XML files parsed via regex to extract<Frame>,<Button>elements withname=,mixin=, andinherits=attributes, producing frame global names with type and mixin associations. Inheritance is resolved transitively with cycle detection.Interface/AddOns/Blizzard_APIDocumentationGenerated/*.lua— Parsed directly for structured function/event/structure data. Each file is a Lua table withType = "System"(game APIs) orType = "ScriptObject"(widget methods, skipped). Functions include namespace, arguments, returns, andMayReturnNothing. Events includeLiteralNameandPayload. Structures include typedFields. Params with aMixinfield use the mixin name (Lua class) instead of the C++Type. Types are normalized minimally:bool→boolean,cstring→string,luaIndex→number; all other type names (e.g.WOWGUID,fileID,time_t) are kept as-is since they have@aliasdefinitions in Ketho'sBlizzardType.lua. Array params (Type = "table", InnerType = "Foo") produceFoo[]. Generated stubs only fill gaps — functions/structures already covered by Ketho's richer annotations are skipped via name deduplication. Also parsed for classic-only constants (structured{Name, Type, Value}entries) and enumerations ({Name, EnumValue}entries).Interface/AddOns/**/*.lua— All FrameXML Lua files scanned for: top-levelUPPER_SNAKEconstant assignments (classic vs retail diff),LE_*name references (cross-referenced with BlizzardInterfaceResourcesLuaEnum.luafor values), and field/method assignments on frame globals (FrameName.field = rhs,function FrameName:method(...)) to infer field types. Also detectsPanelTemplates_SetNumTabscalls to injectnumTabs/selectedTabfields.
5. warcraft.wiki.gg
warcraft.wiki.gg provides community-maintained API documentation.
Function names are discovered by querying the MediaWiki API for the API_functions, API_functions/Removed, API_functions/deprecated, and API_functions/Noflavor categories. Names that duplicate a Blizzard API namespace function (e.g. GetAddOnMetadata shadowed by C_AddOns.GetAddOnMetadata) are filtered unless they still exist as bare globals in BlizzardInterfaceResources' GlobalAPI.lua.
Page names from all three passes are collected upfront and batch-fetched in a single HTTP POST to Special:Export (the endpoint is behind Cloudflare, which rejects concurrent requests, so this is intentionally one request), then distributed to each processor:
The raw Special:Export XML dump is the single most expensive step (~25–40s). It is persistently cached under the platform cache directory ($XDG_CACHE_HOME/~/.cache/wowlua-ls/ on Linux/macOS, %LOCALAPPDATA%\wowlua-ls\ on Windows) with a 24-hour TTL. The cache is keyed by a hash of the requested page-name set — not by parsing logic — so iterating on parse_wikitext() or stub formatting reuses the cached dump and only a change to which pages are requested forces a re-fetch. Set the WOWLUA_LS_REFRESH_WIKI environment variable to force a fresh fetch regardless of cache age.
- Classic-only APIs — Wiki pages for APIs in
(classic_era ∪ classic) \ retailare parsed to extract parameter types, names, and return types, generating typed stubs inClassicGlobals.lua. - Wiki-documented globals — Function names from the wiki category query are parsed with
parse_wikitext()to generateWikiGlobals.lua. Functions without a wiki page or whose markup can't be parsed get a barefunction name(...) endstub with a doc link. - Widget method enrichment — Vendor widget stubs that have a doc link but no annotations are enriched by parsing type annotations via
parse_widget_wiki_annotations().
Wiki parsing handles {{apisig|...}} templates, == Arguments == / == Returns == sections, {{apitype|type|nilable}} type annotations, embedded <!-- luals ... --> blocks, optional parameters [, param], and redirect resolution.
6. Local overrides
Hand-written override files in stubs/overrides/ take precedence over vendor stubs when matched by filename stem. These handle cases that require wowlua-ls-specific annotations not expressible in standard LuaLS (generics, intersections, variadic types, etc.). Currently 26 files:
| File | Purpose |
|---|---|
AceAddon-3.0.lua | AceAddon library stubs |
AceGUI-3.0.lua | AceGUI library stubs |
CreateFrame.lua | Intersection types: CreateFrame(..., template) → T & Tp |
GetCursorInfo.lua | Cursor info return type overloads |
HookScript.lua | Event handler hook typing |
IsObjectType.lua | @type-narrows for IsObjectType() → frame subclass narrowing |
LibStub.lua | Library version management |
Mixin.lua | Variadic generics: Mixin(T, ...M) → T & ...M |
NamePlateBaseMixin.lua | Base mixin for name plates |
RuntimeMissingGlobals.lua | Globals used by addons but not in BlizzardInterfaceResources |
SetScript.lua | Contextual callback typing with event-param narrowing |
WorldFrame.lua | Global frame instance |
coroutine.lua | Coroutine yield/resume types |
debugstack.lua | Debug stack trace function |
ipairs.lua | Generic iterator with K!, V! (non-nil keys/values) |
newproxy.lua | Userdata proxy creation |
next.lua | Generic next iterator with K!, V! |
pairs.lua | Generic iterator with K!, V! (non-nil keys/values) |
PlaySound.lua | String channel override for uiSoundSubType (Blizzard docs say enum, Lua takes strings) |
pcall.lua | Generic success/error tuple returns |
pcallwithenv.lua | Generic pcall variant with environment |
plugin_api.lua | Plugin diagnostic API types |
select.lua | returns<F> projection for variadic return truncation |
string_match.lua | Pattern matching return types |
table.lua | Generic overloads for insert, remove, etc. |
unpack.lua | Variadic unpacking with ...T syntax |
Generated stub files
The pipeline produces these intermediate Lua files (written to a temp directory for scanning, not persisted):
| File | Source |
|---|---|
GlobalStrings.lua | wago.tools db2/GlobalStrings (latest retail build, enUS locale) |
GlobalColors.lua | wago.tools db2/GlobalColor (~360 colorRGBA objects + _CODE string variants) |
GlobalVariables.lua | BlizzardInterfaceResources global name lists (names not covered by GlobalStrings or GlobalColors) |
ClassicGlobals.lua | BlizzardInterfaceResources diff + wiki scraping + APIDocumentation + LE_* constants + XML frame globals |
WikiGlobals.lua | Wiki category query function names + wiki scraping |
BlizzardAPI.lua | Blizzard APIDocumentationGenerated functions (deduped against Ketho) |
BlizzardStructures.lua | Blizzard APIDocumentationGenerated structure types (deduped against Ketho) |
BlizzardEvents.lua | Blizzard APIDocumentationGenerated events |
Enum.lua | APIDocumentation enumerations merged with LuaEnum.lua categories |
CVar.lua | BlizzardInterfaceResources CVars.lua string literal union |
Constants.lua | LuaEnum.lua Constants table (57 sub-tables with typed fields) |
Output artifacts
| File | Format | Content |
|---|---|---|
stubs/precomputed.bin.zst | Magic (0x574F575F) + Version (4B) + zstd-9 payload | Bincode-serialized PrecomputedStubs (PreResolvedGlobals + ClassDecl + ExternalGlobal) |
stubs/precomputed-files.bin.zst | Version (4B) + zstd-9 payload | Bincode-serialized HashMap<String, String> of stub file contents for go-to-definition |
stubs/precomputed-provenance.txt | Text | Generation timestamp, source repo, commit hash, symbol/function/table/file counts |
The main blob contains the fully resolved type database (~150k symbols, ~45k functions, ~24k tables, ~21k classes, ~103k globals). The files blob contains the source text of all referenced stub files (~2,800 files) so the LSP can support go-to-definition into stub code.
The blob version is incremented whenever PreResolvedGlobals, ClassDecl, ExternalGlobal, or any serialized type changes shape.
Loading
The embedded-stubs Cargo feature (default on) bakes both blobs into the binary via include_bytes!. Without the feature (--no-default-features), blobs are loaded at runtime from a stubs/ directory next to the executable — used for universal editor plugin packages that share one copy of stubs across per-platform binaries.
Validation
Before writing, the pipeline validates minimum counts (symbols ≥ 50k, functions ≥ 20k, tables ≥ 10k, files ≥ 1k, globals ≥ 50k, classes ≥ 10k) to catch truncated data from network failures or upstream structure changes.
The dump-stubs CLI subcommand outputs every global name and its resolved type as a tab-separated list, sorted alphabetically. This is useful for diffing before and after stub regeneration to catch regressions:
# Save baseline before changes
wowlua_ls dump-stubs > before.txt
# After regenerating stubs, diff
wowlua_ls dump-stubs > after.txt
diff before.txt after.txt