Diagnostics
wowlua-ls ships 80+ diagnostics covering type safety, annotation correctness, code quality, and WoW-specific checks. Each one is individually suppressible and configurable.
How diagnostics work
Diagnostics run automatically as you type. They're grouped by severity:
- Error: syntax errors, and
safety-limitwhen analysis of a file stopped early - Warning: likely a bug or an annotation problem
- Hint: code quality suggestions, unused variables, style issues
Each diagnostic has a code (like type-mismatch or unused-local) that you use to suppress or configure it. This page covers the most common ones; see All Diagnostics for the complete list.
Quick fixes
Many diagnostics offer quick fixes (code actions — the lightbulb / Ctrl+. in most editors). Every diagnostic also always offers the three suppression actions (disable on this line / for this line above / for this file), and codes with two or more fixable instances get a "Fix all in this file" batch action.
| Diagnostic | Quick fix |
|---|---|
undefined-global | Add local declaration · Add to allowed read globals in .wowluarc.json |
create-global | Add local declaration · Add to allowed write globals in .wowluarc.json |
undefined-field / inject-field | Add ---@field to the class |
missing-fields | Fill required fields with placeholders |
missing-parameter | Add a nil argument |
redundant-parameter | Remove the extra argument(s) |
redundant-return | Remove the redundant return |
redundant-value / redundant-return-value | Remove the surplus value(s) |
not-precedence | Add parentheses (around the comparison, or around not) |
count-down-loop | Add an explicit -1 step |
wrong-flavor-api | Wrap in a WOW_PROJECT_ID flavor guard |
invalid-op | Provide an or 0 / or "" fallback for a possibly-nil operand |
secret-* | Wrap the statement in a canaccessvalue guard (canaccessallvalues for several values) |
| type-mismatch family | Insert an --[[@as T]] cast |
unused-local | Prefix the name with _ |
incomplete-signature-doc | Generate the missing @param / @return annotations |
trailing-space | Remove the trailing whitespace |
The config-editing fixes (undefined-global, create-global) only appear when a .wowluarc.json governs the file. The wrong-flavor-api guard compares WOW_PROJECT_ID against one constant for each flavor that runs the code (your target flavors, minus any an enclosing guard rules out) and has the API: WOW_PROJECT_MAINLINE (Retail), WOW_PROJECT_CLASSIC (Classic Era), WOW_PROJECT_CAMELOT (Forever). It isn't offered when Classic is among them, since Classic spans several expansion constants. The secret-* guard is offered only when each flagged value is a name or field chain and the statement is a call, assignment, if, while, or repeat (not a local declaration or return).
Suppressing diagnostics
Inline
Suppress a diagnostic on the same line by appending disable-line at the end:
local unused = computeSomething() ---@diagnostic disable-line: unused-localWhen the diagnostic lands on an annotation line itself (for example class-shadows-builtin on a ---@class), append the directive to that same comment line:
---@class Frame ---@diagnostic disable-line: class-shadows-builtin
---@field extra stringPrevious line
---@diagnostic disable-next-line: unused-local
local unused = computeSomething()Per-block
---@diagnostic disable: create-global
MY_GLOBAL = true
OTHER_GLOBAL = false
---@diagnostic enable: create-globalPer-project
In .wowluarc.json:
{
"diagnostics": {
"disable": ["inject-field", "unused-local"]
}
}LuaLS compatibility aliases
For compatibility with LuaLS suppress comments, these aliases are accepted:
| Alias | Maps to |
|---|---|
invisible | access-private, access-protected |
param-type-mismatch | type-mismatch |
return-type-mismatch | return-mismatch |
Type safety diagnostics
These catch type errors - the most valuable diagnostics for finding real bugs.
type-mismatch Warning
Argument type doesn't match the function's @param annotation:
---@param name string
function greet(name) end
greet(42) -- type-mismatch: expected string, got numberreturn-mismatch Warning
Return type doesn't match the function's @return annotation:
---@return string
function getName()
return 42 -- return-mismatch
endfield-type-mismatch Warning
Assignment to a field doesn't match its @field type:
---@class Config
---@field name string
---@type Config
local cfg = {}
cfg.name = 42 -- field-type-mismatchassign-type-mismatch Warning
Reassignment doesn't match the variable's @type annotation:
---@type string
local x = "hello"
x = 42 -- assign-type-mismatchgeneric-constraint-mismatch Warning
Generic argument doesn't satisfy the class constraint:
---@class Box<T: number|string>
---@type Box<boolean> -- generic-constraint-mismatch
local b = {}The same check applies to constrained parameterized aliases (@alias Name<T: Constraint> ...):
---@class Frame
---@alias Wrapper<T: Frame> { value: T }
---@type Wrapper<number> -- generic-constraint-mismatch
local w = {}need-check-nil Warning Off by default
Field/method access on a possibly-nil value:
---@param name string?
function greet(name)
print(name:upper()) -- need-check-nil
endEnable in .wowluarc.json: "diagnostics": { "enable": ["need-check-nil"] }
grouped-return-mismatch Warning
Return values don't match any declared tuple-union case:
---@return (string, number) | (nil, nil)
function example()
return "hello", nil -- grouped-return-mismatch
endArgument diagnostics
missing-parameter Warning
Required argument not provided:
---@param a number
---@param b number
function add(a, b) end
add(1) -- missing-parameter: bredundant-parameter Warning
Extra arguments beyond what the function accepts:
---@param x number
function single(x) end
single(1, 2) -- redundant-parametercannot-call Warning
Calling a value whose type is known to be non-callable:
---@type table
local tbl = {}
tbl() -- cannot-call: cannot call a value of type 'table'
---@type number
local n = 5
n() -- cannot-callinvalid-op Warning Off by default
Arithmetic, concatenation, or a </>/<=/>= comparison applied to incompatible types, or # applied to a value that has no length. Common when + is used instead of .. for string concatenation:
error("Missing context: " + tostring(field))
-- invalid-op: cannot apply '+' to 'string' and 'string' (use '..' to concatenate)
local x = true + 1
-- invalid-op: cannot apply '+' to 'true' and 'number'
local count = 3
print(count > "5")
-- invalid-op: cannot apply '>' to 'number' and 'string'Enable in .wowluarc.json: "diagnostics": { "enable": ["invalid-op"] }
Return diagnostics
missing-return-value Warning
Return with fewer values than declared:
---@return string, number
function getInfo()
return "hello" -- missing-return-value
endmissing-return Warning
Function with @return but no return statement on some paths:
---@param name string?
---@return string
function getName(name)
if name then
return name
end
-- missing-return (falls off without returning)
endredundant-return-value Warning
Returning more values than declared:
---@return string
function getName()
return "hello", 42 -- redundant-return-value
endimplicit-nil-return Hint Off by default
Bare return in a function with all-optional @return types:
---@param loaded boolean
---@return string?
function maybeName(loaded)
if not loaded then return end -- implicit-nil-return
return "name"
endGlobal and field diagnostics
undefined-global Warning
Reference to an unresolved global name:
print(MyUnknownGlobal) -- undefined-globalSuppress with globals.read in .wowluarc.json for known external globals. Dynamic global patterns like _G["PREFIX" .. key] = value are detected automatically: reads matching the prefix won't trigger this diagnostic.
undefined-field Warning
Accessing a field that doesn't exist on a @class:
---@class Player
---@field name string
---@type Player
local p = {}
print(p.level) -- undefined-fieldA field read used as a defensive existence check is not flagged - it is probing whether the field exists, not assuming it does. This covers the left operand of and/or, the condition of an if/while, and the access that such a guard protects:
local nodeID = button.GetNodeID and button:GetNodeID() -- no undefined-field
if frame.SetBackdrop then
frame:SetBackdrop(nil) -- guarded by the condition: no undefined-field
end
local cache = obj.Custom or obj.Fallback -- the `or` fallback idiomA deeper access on a now-known field is still checked, so genuine typos are not hidden: if obj.cfg then obj.cfg.typo end still flags typo.
inject-field Hint
Setting a field not declared on a @class:
---@class Player
---@field name string
---@type Player
local p = {}
p.level = 10 -- inject-fieldcreate-global Warning
Creating a global variable (assignment or function definition without local):
MyGlobal = true -- create-global
function GlobalFunc() end -- create-globalSuppress with globals.write in .wowluarc.json.
missing-fields Warning
Missing required fields when constructing a @class table:
---@class Config
---@field name string
---@field debug boolean
---@type Config
local cfg = { name = "test" } -- missing-fields: debugAnnotation diagnostics
invalid-class-parent Warning
Inheriting from a primitive or literal type instead of a class:
---@class Nums : number -- invalid-class-parent
---@class Lit : "foo" -- invalid-class-parent
---@class Union : 1 | 2 | 3 -- invalid-class-parentundefined-doc-class Warning
Undefined parent in @class Foo : Parent:
---@class Child : NonexistentParent -- undefined-doc-classundefined-doc-name Warning
Undefined type name in any annotation:
---@param x NonexistentType -- undefined-doc-name
function foo(x) endunknown-cast-variable Warning
@cast names a variable that isn't in scope, so the cast has no effect:
local parent = frame:GetParent()
---@cast parnet Frame -- unknown-cast-variable (typo: should be 'parent')undefined-doc-param Warning
@param name doesn't match any function parameter:
---@param nme string -- undefined-doc-param (typo: should be 'name')
function greet(name) endduplicate-doc-param / duplicate-doc-field / duplicate-doc-alias Warning
Duplicate annotation declarations.
circle-doc-class Warning
Circular inheritance chain:
---@class A : B
---@class B : A -- circle-doc-classmalformed-annotation Warning
Unknown or incomplete ---@ annotation.
unknown-diag-code Warning
Unknown code in @diagnostic directives.
Code quality diagnostics
unused-local Hint
Unreferenced local variable. An unused local function (local function f() or local f = function() end) reports unused-function instead.
unused-function Hint Off by default
A function (global, method, or local) that nothing in the workspace references. Enable with "diagnostics": { "enable": ["unused-function"] } in .wowluarc.json. See the reference for what counts as a reference.
redefined-local Warning
Same-scope local variable redefinition.
shadowed-local Hint
Local variable shadows a variable from an outer scope. Fires for local declarations, for-loop variables, and function parameters. Suppressed for _-prefixed names.
unreachable-code / code-after-break Hint
Code after return or break.
deprecated Warning
Call to a function marked @deprecated.
discard-returns Warning
Ignoring the return value of a @nodiscard function.
not-precedence Hint
not x < y parses as (not x) < y, which is almost certainly not what you meant.
count-down-loop Warning
Numeric for-loop step direction doesn't match start/end:
for i = 10, 1 do -- count-down-loop: needs step -1
print(i)
endStyle diagnostics
empty-block Hint
Empty if/while/for/repeat body. Suppressed if the body contains a comment.
redundant-return Hint
Bare return as the last statement of a function's top block.
trailing-space Hint
Line ends with whitespace.
WoW-specific diagnostics
wrong-flavor-api Warning
API call not available in all declared project flavors. See Flavor Filtering.
secret-arithmetic / secret-comparison / secret-condition / secret-table-key / secret-argument / secret-access Warning
Retail 12.x only. Flags operations that error when a value is secret: arithmetic, comparisons (including numeric for bounds), testing a secret boolean, using it as a table key, passing it to an API that rejects secrets from addon code (most of the API, math.* included), and indexing it (name:upper() included), calling it, or taking its length. Guard with canaccessvalue or issecretvalue. See Secret Values.
access-private / access-protected Warning
Accessing a private or protected field from outside its visibility scope.
Strict typing diagnostics Hint
These are off by default. They mark every value whose type is unknown (no type at all, any, or any?), so you can find the gaps in your type coverage:
| Code | Fires on |
|---|---|
unknown-param-type | A parameter, including _ and self, or a reassignment of one |
unknown-return-type | A returned value, even in a function with an @return annotation, or a file's annotated return value |
unknown-local-type | A local, for, or global variable, or a reassignment of one |
unknown-field-type | A class field's assigned value, or its ---@field type |
An explicit any annotation (---@param x any, ---@return any, ---@type any on a declaration, ---@field name any) is reported as "is annotated any".
Enable in .wowluarc.json:
{
"diagnostics": {
"enable": ["unknown-param-type", "unknown-return-type"]
}
}