The compiler, package manager and tooling ship as one Rust binary,
wowlua. This chapter specifies its internal architecture. It is normative
for module boundaries and pipeline order, and advisory for library choices.
13.1 Requirements
| Requirement | Target |
|---|---|
| Language | Rust stable, edition 2024. MSRV: latest stable minus 2 releases. |
| Platforms | macOS (arm64, x86_64), Windows (x86_64, arm64), Linux (x86_64, arm64, musl static) |
| Distribution | Single self-contained binary. No runtime dependencies besides an optional system git. |
| Performance | Full check + build of 100k lines of wowlua in under 1 s on a 2024 laptop. Incremental rebuild of one changed file in under 100 ms. |
| Determinism | Byte-identical output for identical inputs, compiler version and options. No hash-map iteration order leaks into output. |
| Robustness | Never panics on any input. Randomized robustness tests (random bytes, token soup, mutated sources) through parser, lowering, emitter and formatter are part of CI. Deeply nested input is an error (E5001), never a stack overflow. |
13.2 Workspace layout
wowlua/├── Cargo.toml # workspace├── crates/│ ├── wowlua-cli/ # binary "wowlua": argument parsing, terminal UI│ ├── wowlua-driver/ # orchestration: build graph, profiles, flavors, caching, watch│ ├── wowlua-span/ # FileId, Span, SourceDb, line index│ ├── wowlua-diagnostics/ # Diagnostic model, codes, rendering (human + JSON)│ ├── wowlua-syntax/ # lexer, parser, AST, visitors, Lua 5.1 grammar tests│ ├── wowlua-sema/ # name resolution, module graph, types, checker, const-eval│ ├── wowlua-lower/ # wowlua AST + semantic info → Lua IR│ ├── wowlua-luair/ # Lua 5.1 IR, optimization passes, emitter, minifier, source maps│ ├── wowlua-runtime/ # Lua sources of the __wl runtime, feature-sliced (include_str!)│ ├── wowlua-stdlib/ # wowlua:* modules (written in wowlua) + their .d.wowlua│ ├── wowlua-manifest/ # wowlua.json model, JSON schema, validation, profile merging│ ├── wowlua-flavors/ # flavor table data: interfaces, suffixes, client features, site IDs, deferred-API table│ ├── wowlua-toc/ # TOC generation and parsing│ ├── wowlua-xml/ # WoW UI XML: well-formedness, Script/Include references, path rewriting│ ├── wowlua-pkg/ # resolver, sources (path/git/archive/registry), cache, lockfile│ ├── wowlua-publish/ # packaging (zip), uploaders (CurseForge, Wago, WoWI, GitHub, registry)│ ├── wowlua-types-gen/ # wow-api generator (Blizzard docs + LuaLS converter) → .d.wowlua│ ├── wowlua-test/ # test runner on an embedded Lua 5.1 VM + WoW mock environment│ ├── wowlua-fmt/ # formatter│ └── wowlua-lsp/ # language server├── runtime/ # (symlink or path) Lua runtime sources used by wowlua-runtime├── stdlib/ # wowlua standard library sources├── tests/ # end-to-end tests, corpus tests└── xtask/ # dev automation (schema generation, corpus fetch, release)Dependency direction (an acyclic graph; arrows point from user to dependency):
cli → driver, publish, test, lsp, fmt, types-genlsp → driver, sema, syntax, fmttest → driver (builds with the test profile, then runs mlua)publish → driver, manifest, flavorsdriver → pkg, manifest, toc, xml, lower, sema, luair, runtime, stdlib, flavors, diagnosticspkg → manifest, toc, flavors, diagnosticsmanifest → flavors, diagnostics, spantoc → flavors, spanxml → span, diagnosticslower → sema, luair, syntaxsema → syntax, flavors, diagnostics, spanluair → span, diagnosticssyntax → span, diagnosticsfmt → syntaxtypes-gen → syntax, flavors (+ mlua, to execute Blizzard's documentation tables)runtime, stdlib, flavors (data crates, no dependencies)sema does not depend on stdlib: the driver loads the standard library's
.d.wowlua declarations and sources from wowlua-stdlib and passes them to
sema as ordinary inputs, together with the compiler-known semantics of
framework decorators (a table keyed by @intrinsic id). Only the modules an
addon imports (transitively) are loaded; each compiles to
wowlua/std/<name>.lua with the module ID wowlua:<name>. The environment
variable WOWLUA_STDLIB_DIR replaces the shipped modules with a directory of
<name>.wowlua / <name>.d.wowlua files, for developing the standard library
(decision T79).
A module's .d.wowlua binds exports to intrinsics. A declaration typed any
(@intrinsic("events.on") declare global on: any) only binds: callers see the
module's own type of the export. A declaration with a type of its own replaces
the module's type for callers; it is used where the compiler fills in
arguments, so the caller's signature differs (wowlua:debug's and
wowlua:log's functions, which take their source location).
13.3 Pipeline
manifest ──► resolve deps ──► discover (project + libraries + stdlib) ──► parse (parallel) │ │ └── XML parse ──── locale JSON ──────────┤ ▼ ┌──────────────── for each build configuration (profile, or publish target) ─────────────────┐ │ packager-keyword preprocessing of passthrough files (compat only, per flavor) │ │ ┌──────────────────────────── for each flavor (split mode) ───────────────────────────┐ │ │ │ bind meta constants (profile, flavor, features, config) │ │ │ │ static-condition pre-pass → static pruning (@if, static if-conditions) │ │ │ │ name resolution & scopes (per file) │ │ │ │ module graph: imports + usage analysis → load-time/run-time edges → load order │ │ │ │ declaration collection (+ implicit declarations, locale L type) │ │ │ │ type checking (per function, parallel per module) │ │ │ │ lowering → Lua IR (+ locale files, Bindings.xml model) │ │ │ │ IR passes (fold, DCE, instrument, inline, cache-globals) │ │ │ │ bundling (optional) → hygiene → minification (level) → emission + source maps │ │ │ └──────────────────────────────────────────────────────────────────────────────────────┘ │ │ assemble: merge flavors (shared vs per-flavor files, union TOC order), runtime slices, │ │ libraries, XML rewriting, Bindings.xml, LICENSES.txt, assets, TOC files │ │ output validation ──► atomic write of dist/<profile>/<Addon>/ or dist/targets/<target>/<Addon>/ └─────────────────────────────────────────────────────────────────────────────────────────────┘A build configuration is a profile (for build, dev, install, test) or a
publish target, which selects a profile plus overrides
(Publishing §11.1). Configurations with
identical effective settings share one build.
13.3.1 Stages
| # | Stage | Crate | Input → Output | Notes |
|---|---|---|---|---|
| 1 | Load manifest | manifest | wowlua.json → Manifest |
package.wowlua is checked before schema validation (E4300), then JSON-schema validation, profile inheritance and merging, workspace expansion |
| 2 | Resolve | pkg | Manifest + lockfile → ResolvedGraph |
Fetches sources into the cache. --locked / --frozen / --offline honored. Pins the flavor table version (Package manager §10.6). |
| 3 | Discover | driver | file system → SourceDb |
Project sources (build.src, build.exclude, .gitignore), dependency sources, used standard-library modules, implicit types packages, locale JSON files, XML files, assets |
| 4 | Parse | syntax, xml | bytes → Ast / XmlDoc + diagnostics |
Parallel per file (--jobs; together with each plain Lua file's Lua 5.1 check). Error-recovering. XML <Script>/<Include> references are collected. Parsing is shared by all configurations, except for files changed by stage 5. |
| 5 | Preprocess | driver | passthrough text → text | Only with compat.packager-keywords: BigWigs keyword substitution on passthrough .lua/.xml files, per configuration and flavor (CLI §12.6). Affected files are re-parsed per flavor. |
| 6 | Locales | lower, sema | locale JSON → Lua IR + L type |
Key and placeholder checks, generation of the L record type used by stage 11 |
| 7 | Meta binding | sema | configuration, flavor → ConstEnv |
Values of wowlua:meta exports (for libraries: the consuming addon's addon/build/flavor, the library's own features/config) |
| 8 | Static pre-pass | sema | ASTs + ConstEnv → constant values |
Evaluates const initializers that are static, in syntactic import order. Static conditions may only use wowlua:meta paths, literals and such constants; @if conditions on import statements may only use wowlua:meta paths and literals, so that the per-flavor import graph can be built without type information. |
| 9 | Static pruning | sema | AST + constants → pruned view | Marks dead branches and removed declarations, including imports removed by @if. Later stages skip them. |
| 10 | Resolve names | sema | AST → Scopes, Bindings |
Per file: locals, upvalues, globals, ns fields, imports, decorators. Needs no other module. |
| 11 | Module graph | sema | imports + bindings → ModuleGraph |
Per flavor. Import edges plus usage-analysis edges for globals and addon-table fields, classified load-time / run-time (§13.6.1). Cycle check, lazy bindings, load order. |
| 12 | Collect declarations | sema | → TypeEnv |
Classes, interfaces, enums, aliases, declares in load order, plus implicit declarations: wow-api-<flavor>, WoW's Lua environment, SavedVariables globals, check.globals, AddonNamespace, the L type, and the generated declaration files of workspace addons listed in addon-dependencies. Workspace declaration files are produced from this stage's output, so mutually dependent addons do not need each other's full build. |
| 13 | Type check | sema | → TypeTable + diagnostics |
Bidirectional checking per function body. Dependency and standard-library modules are checked with their own package's check settings. |
| 14 | Lower | lower | AST + sema → lua::Chunk |
Desugaring, classes, decorators, modules (copied vs lazy bindings), debug features, keybinding declarations → Bindings.xml model |
| 15 | IR passes | luair | Chunk → Chunk |
See Optimization |
| 16 | Bundle | luair | many chunks → one or a few chunks | Release layout. XML-referenced modules stay separate. |
| 17 | Hygiene | luair | chunk → chunk | Renames generated identifiers that collide. Runs after bundling, over each final output chunk (Code generation §14.3). |
| 18 | Minify | luair | Chunk → Chunk |
Level-dependent, within the Lua-limit budgets |
| 19 | Emit | luair | Chunk → text + source map |
According to line-mode |
| 20 | Assemble | driver, toc, xml | → output tree | Merges flavors (byte-identical files stored once), union TOC order, runtime slices, meta tables, libraries (entry files, XML-reachable files and licenses only), XML path rewriting, Bindings.xml, LICENSES.txt, assets, TOC files |
| 21 | Validate | driver | output tree → ok / errors | See §13.8 |
| 22 | Write | driver | → dist/ |
Write to a temp directory, then replace the target directory (§13.12) |
Stages 7–19 run once per flavor in split mode, and once in shared mode.
Stages 1–4 and 6 are shared by all configurations and flavors.
13.4 Core data structures
SourceDbstores file contents,FileId, path, line index and hash. Spans are(FileId, u32 start, u32 end)byte offsets.- Interning. Identifiers and string literals use a global interner
(
Symbol(u32)). Types are hash-consed in aTypeInterner(TypeId(u32)). - AST. Typed enums per node kind, allocated in per-file arenas. Every node
has a
NodeIdand aSpan. Comments are kept as trivia attached to the following token. The formatter needs them, and they are discarded at emission unless kept by the profile. - Side tables. Semantic results are stored in maps keyed by
NodeId(IndexVec/FxHashMap), not in the AST. The AST is immutable after parsing. - Lua IR. A separate, small AST that can only represent Lua 5.1. Every
statement and expression carries an
Origin(source span, or "generated with anchor span"), which drives line preservation and source maps. - Diagnostics.
Diagnostic { code, severity, message, primary: Span, labels: Vec<(Span, String)>, notes, help, fixes: Vec<Fix> }. Fixes are machine-applicable edits, used by the LSP andwowlua fix.
13.5 Front end
13.5.1 Lexer
A hand-written, byte-oriented lexer. It implements Lua 5.1's lexical
grammar exactly, including long brackets, numeral reading and escape rules,
plus the wowlua tokens. Contextual keywords are lexed as Name and recognized
by the parser. Backtick strings produce a token stream of fragments and
embedded-expression boundaries.
13.5.2 Parser
Hand-written recursive descent, with Pratt parsing for expressions (the precedence table in Syntax §1.4.6).
- Contextual keywords are recognized with one or two tokens of lookahead, following the rules in Syntax §1.2.5.
- Type contexts have their own sub-parser.
>>,>=and>>=are split into their component tokens there. - Error recovery. On an error the parser synchronizes at statement
boundaries (
local,function,end,if,for,while,repeat,return,class, …). It producesErrornodes and continues, so that one run reports multiple independent errors and the LSP gets a usable tree. - Lua 5.1 quirks. It reports the "ambiguous syntax (function call x new statement)" error exactly where Lua 5.1 does, so that pure Lua files have the same accept/reject behavior.
13.5.3 Superset conformance
The parser has a Lua 5.1 compatibility suite:
- the official Lua 5.1 test suite files must parse,
- a corpus of popular open-source addons (pinned commits, fetched by
xtask corpus) must parse, check in basic mode with zero errors, and - round-trip: for every corpus file,
emit(lower(parse(file)))compiled by a reference Lua 5.1 compiler with debug info stripped must produce the same bytecode as the original file. Inpreserveline mode the line info must be identical as well, so in-game error lines match the source.
cargo xtask corpus fetch downloads the pinned corpus (xtask/corpus.txt,
checked by SHA-256) and cargo xtask corpus run runs these checks plus the
formatter's (meaning preserved, idempotent). A scheduled CI job runs the full
corpus; regular CI runs tests/corpus-sample/ (decision T66).
13.6 Semantic analysis
- Scopes. Lua's block scoping is modelled exactly, including the rule that a
localis visible only after its declaration statement, andrepeat … untilcondition scoping. The binder records each local's captures and assignments, which narrowing invalidation and const checks use. - Module graph. Built per flavor after static pruning and name resolution, and before type checking (§13.6.1).
- Type checker. Bidirectional:
check(expr, expected)andinfer(expr), with flow-sensitive narrowing computed on a per-function control-flow graph,- generic inference by unification of argument types against parameter types, with widening of literals at inference boundaries,
- overload resolution by trying candidates in order.
The checker never mutates the AST. Unknown or erroneous types become
Error, which is assignable to everything, so one error does not cascade.
- Const evaluation. A small interpreter over constant expressions,
covering numbers, strings, booleans,
nil, arithmetic with Lua 5.1 semantics (double precision,%asa - floor(a/b)*b) and concatenation with Lua's number formatting (%.14g). It is used forconst, enum values, static conditions and folding.
13.6.1 Load-order analysis
This implements the load-order rules of Modules §5.4:
- Edges. An edge
A → B(A must be considered before B) comes from:- an
importin B of module A (the import's bindings mark which uses count), - usage analysis: B reads a global or an addon-table field (
ns.Foo, including through locals aliasingns) that A assigns at its top level. This also covers plain.luafiles and XML (globals and templates an element needs when it is created, named frames as definitions).
- an
- Classification. Each use is load-time if it can execute while B's
file loads: in a top-level statement, or in a function that is reachable
from top-level code through the call graph, followed across modules using
per-function summaries of what each function may call and read. A function value passed to
a parameter marked deferred in the declarations (e.g.
SetScripthandlers,HookScript,C_Timer.After,hooksecurefunc,@event/@slash/ lifecycle handlers) does not make its body load-time. A function passed to a parameter marked immediate (e.g.pcall,xpcall,securecallfunction,table.sortcomparators) does. A function passed to a package function is load-time only if the callee's summary says it calls (or forwards) that parameter; passed to an unknown callee it is treated as load-time. The deferred/immediate markers come from the flavor table and the declarations. Guarded uses (existence tests and the code they guard) do not create edges (decision T70). Summaries tag code that runs only when a parameter is not a function; the call-graph walk keeps, for each function reached, the parameters every reaching call passed a function for, revisits a function when that set shrinks, and skips the tagged code for those parameters (decision T207). - Order. A deterministic topological sort over load-time edges. Among
ready files, the order of
build.load-order(into whichwowlua initwrites the old TOC order), then byte-wise path order, breaks ties. - Cycles. Strongly connected components (Tarjan's algorithm). A cycle of
load-time edges alone is error E1014; the diagnostic shows every edge of the
cycle and the call chain that makes it load-time. Over all edges, a run-time
import inside a component becomes a lazy binding, lowered as a field
read on the imported module's export table (
__wl_m3.Foo) instead of a local copy; outside cycles, run-time imports still order the files (decision T72). Usage edges that contradictbuild.load-orderare dropped with W1035 before cycles are searched. - Ambiguity. Dynamic names (
_G["x" .. i]), several writers of the same global or field, orsetfenvmake an edge undecidable. The files involved keep their relative source order and warning W1033 names the cause, unless the layout already fixes their order: both have a place inbuild.load-order(directly or through the XML file that loads them), or one XML file loads both (decision T205). - Late definitions. A load-time read of a name that is only defined at run time is W1034. In a load-on-demand addon with required addon dependencies, fields of tables the addon does not define (other than its SavedVariables) are exempt: their owner has run before (decision T206).
13.6.2 Lints
Lints run in wowlua check, wowlua build and wowlua lint (which reports
only lints, and errors that stop the analysis) on .wowlua files, and on
.lua files when check.lua is set. --!nolint turns them off for a file;
check.lints changes their level (decisions T90–T93).
- W1003, unused binding. A local,
const, local function, destructured field, import, parameter or loop variable that is never read. Assigning a local is not a read. Names starting with_andselfare exempt, as are exported and decorated declarations. A use in a type position (an imported type) or in a conditionally eliminated branch counts. Parameters and loop variables are positional: only those after the last used one of the same list are reported. Top-levelfunction f()declarations of a module are hoisted locals and are reported like local functions. - W1004, shadowing. A local, local function, loop variable or parameter
(also of a nested function) that reuses the name of a visible local of an
enclosing scope. Redeclaring a local in the same block (
local x = f(x)) is not reported. - W1005, unreachable code. The first statement after
return,break,continue, or a call whose type returnsnever(error), or after anifwhose branches all end that way; once per region. Reported by the type checker, so not in--!nocheckfiles.
Fixes (wowlua fix, wowlua lint --fix, the LSP). A statement whose
names are all unused is removed when its values have no side effects
(literals, names, functions, and tables of those); its line goes with it when
nothing else is on it. Other unused locals, parameters and loop variables are
renamed _name, unless the file already uses _name. An unused import name
is removed; an import without used names is removed when it has no load-order
effect (import type, wowlua: modules), and otherwise becomes
import _ from "…", which keeps the load order. Destructured fields, W1004 and
W1005 get no fix. Fixes are applied in rounds until none is left, because one
fix can make another one possible.
13.7 Back end
Covered in detail by Code generation and Optimization. Architecture rules:
- Lowering produces Lua IR only. It never produces text.
- Every IR pass is a function
fn(&mut Chunk, &PassCtx). The pass list is chosen by the profile and minification level. Passes are individually testable. - The emitter is the only component that produces Lua text. It handles
precedence-based parenthesization, statement separators (
;where Lua would otherwise see an ambiguous call), line preservation and source maps.
13.8 Output validation
Before anything is written, the assembled output must pass these checks. A failure is an internal compiler error (exit code 101) if caused by codegen, or a user error otherwise:
- References. Every file referenced by a TOC exists. Every output Lua or XML
file is reachable: listed in a TOC, or referenced through a
<Script>/<Include>chain starting at a TOC-listed XML file.Bindings.xml(loaded by the client automatically),LICENSES.txtand assets are exempt. Violations are E5002. - Lua syntax. Every emitted Lua file parses as Lua 5.1 with the compiler's own Lua 5.1 parser. In the compiler's own CI it is also loaded by the embedded Lua 5.1 VM.
- Lua limits. No function exceeds Lua 5.1's limits: 200 active locals, 60 upvalues, about 250 registers ("function or expression too complex"), 200 nested syntax levels (C-call depth, including the extra levels added by bundle wrappers), 262 143 constants, and jump offsets of ±131 071 instructions. Optimization passes stay within budgets for these limits (Optimization §15.2). A user function that alone exceeds a limit is error E5001, naming the function and the limit.
- XML. Well-formed, and
<Script file="…">/<Include file="…">references resolve after rewriting (Code generation §14.8.4). - TOC. Interface numbers are present for every flavor, file paths use the
exact case and
\separators, and no line exceeds 1024 characters (E4303). - SavedVariables names are valid Lua identifiers and unique across the workspace's addons (E4120).
- Assets. Every path folded by
asset()exists among the output's assets (E4307). - Paths. No two output paths are equal or differ only in case (E4311 for collisions with reserved names, E4308 for case collisions).
- Licenses. Every embedded library contributed license text to
LICENSES.txt; a library without a recognizable license file or header is warning W4306.
13.9 Caching and incremental builds
-
Within one build. A split multi-flavor build compiles a file once for all flavors where its lowering context is the same apart from
wowlua:metaand its lowering does not readwowlua:meta. -
wowlua dev. The session keeps, in memory, what it compiled between builds:- each compiled file (code, source map, diagnostics), by its source path, file id, content hash and lowering context, as within one build;
- the Lua 5.1 check of each plain
.luasource, by its path, file id and content hash; - for each plain
.luasource that parsed cleanly and that no flavor type-checks (nocheck.lua), what the build reads of it besides its code: its load-order usage and--!nolint, by its path, file id and content hash. A rebuild does not parse such a file again unless a flavor has to compile it again. Withrename-privateor dead-module removal on, every source is parsed.
A rebuild takes from it what compiling again would give, so it writes the same files and reports the same diagnostics as a fresh build. Files that read
wowlua:metaare compiled every time, sincebuild.idchanges on every save. A change to the manifest, the lockfile, the dependencies or the build request empties the cache. Entries a build did not use are dropped..wowluasources, changed sources and type-checked plain Lua are parsed and analyzed on every build. A rebuild still replaces the AddOn folder as a whole, but links each file whose bytes are those of the last build (and that is on disk as that build left it) from the old folder instead of writing it, and rewrites only the source maps that changed. -
Phase 2. Persistent per-module cache in
dist/.cache/keyed by the module's content hash, the compiler version, the effective configuration hash (profile, target overrides, feature set), the flavor, the flavor-table version, the locked types-package versions, the lockfile hash, and the export-interface hashes of its imports. A module is re-checked only if its own key changes. Dependents are re-checked only if its export interface hash (the types of its exports) changes. A different compiler version invalidates the whole cache. -
LSP. Uses the same queries in a demand-driven database (
salsais the intended framework), sharing code with the batch compiler.
13.10 Suggested crates
| Purpose | Crate |
|---|---|
| CLI | clap (derive) |
| JSON manifest, preserving formatting on edit | serde, serde_json, jsonc-parser-style CST editing for wowlua add (json-patch fallback) |
| JSON schema | schemars (generated from the manifest types) |
| Diagnostics rendering | codespan-reporting |
| Parallelism | std::thread worker pools with a memory budget (wowlua-driver/src/parallel.rs): parsing, lowering and finishing, and linting run per file on workers and are merged in file order |
| Hashing | rustc-hash (in-memory), blake3 (content hashes), sha2 (lockfile checksums) |
| Deterministic maps | indexmap |
| Semver and resolution | semver, pubgrub |
| Git | system git via std::process (MVP), gix later |
| HTTP | ureq with rustls |
| Archives | zip, flate2, tar |
| File watching | notify |
| Globs | globset, ignore |
| Embedded Lua 5.1 (tests, validation) | mlua with features lua51, vendored |
| LSP | tower-lsp (or lsp-server) |
| Snapshot tests | insta |
| Deep recursion | stacker (grows the stack per nesting level) |
| Logging | tracing |
13.11 Testing the compiler
| Layer | Method |
|---|---|
| Lexer and parser | Unit tests, accept/reject parity with the embedded Lua 5.1 on hand-picked and randomized inputs, randomized robustness tests (no panics, recovery terminates) |
| Superset | Lua 5.1 test suite, addon corpus, bytecode round-trip (§13.5.3) |
| Type checker | .wowlua test files with inline expectations (--~ E2001 on the offending line) |
| Lowering | Snapshot tests of emitted Lua per feature, plus differential execution: run the wowlua source's expected behavior as embedded Lua and compare printed output |
| Minifier | Every corpus file minified at every level must behave identically in the VM (the same printed trace for test scripts) |
| End to end | Example projects built for every flavor. The output is validated and loaded in the mock WoW environment. |
| Package manager | Local git and HTTP fixtures, resolver property tests |
13.12 Platform considerations
- Paths. Internally, paths are UTF-8 with
/. TOC file lists use\, zip entries use/. Output paths that collide case-insensitively are errors on every platform (E4308), because WoW on Windows would load only one of them. - Long paths. On Windows, file operations use extended-length (
\\?\) paths, so deepdist/trees work without the 260-character limit. - Atomic replacement. On macOS and Linux, the output directory is replaced
by renaming the finished temp directory over it. Windows cannot rename over a
non-empty directory, so the old directory is first renamed aside, the new one
renamed into place, and the old one deleted. If the process dies in between,
the next build cleans up the leftover
*.wowlua-olddirectory. - Links.
wowlua dev --linkuses symlinks on macOS and Linux and directory junctions on Windows (no administrator rights needed). If neither can be created, it falls back to copying with warning W4309.
13.13 Privacy and messages
- No telemetry. The compiler never sends usage data. Network access happens
only for dependency fetching, registry access,
wowlua types sync, publishing, and the site-version lookups publishing needs. - Messages are English only. Diagnostic codes are stable and searchable.
- Offline documentation.
wowlua explain <code>texts are embedded in the binary and work offline.