Skip to content

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-limit when 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.

DiagnosticQuick fix
undefined-globalAdd local declaration · Add to allowed read globals in .wowluarc.json
create-globalAdd local declaration · Add to allowed write globals in .wowluarc.json
undefined-field / inject-fieldAdd ---@field to the class
missing-fieldsFill required fields with placeholders
missing-parameterAdd a nil argument
redundant-parameterRemove the extra argument(s)
redundant-returnRemove the redundant return
redundant-value / redundant-return-valueRemove the surplus value(s)
not-precedenceAdd parentheses (around the comparison, or around not)
count-down-loopAdd an explicit -1 step
wrong-flavor-apiWrap in a WOW_PROJECT_ID flavor guard
invalid-opProvide an or 0 / or "" fallback for a possibly-nil operand
secret-*Wrap the statement in a canaccessvalue guard (canaccessallvalues for several values)
type-mismatch familyInsert an --[[@as T]] cast
unused-localPrefix the name with _
incomplete-signature-docGenerate the missing @param / @return annotations
trailing-spaceRemove 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:

lua
local unused = computeSomething() ---@diagnostic disable-line: unused-local

When the diagnostic lands on an annotation line itself (for example class-shadows-builtin on a ---@class), append the directive to that same comment line:

lua
---@class Frame ---@diagnostic disable-line: class-shadows-builtin
---@field extra string

Previous line ​

lua
---@diagnostic disable-next-line: unused-local
local unused = computeSomething()

Per-block ​

lua
---@diagnostic disable: create-global
MY_GLOBAL = true
OTHER_GLOBAL = false
---@diagnostic enable: create-global

Per-project ​

In .wowluarc.json:

json
{
  "diagnostics": {
    "disable": ["inject-field", "unused-local"]
  }
}

LuaLS compatibility aliases ​

For compatibility with LuaLS suppress comments, these aliases are accepted:

AliasMaps to
invisibleaccess-private, access-protected
param-type-mismatchtype-mismatch
return-type-mismatchreturn-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:

lua
---@param name string
function greet(name) end

greet(42) -- type-mismatch: expected string, got number

return-mismatch Warning ​

Return type doesn't match the function's @return annotation:

lua
---@return string
function getName()
    return 42 -- return-mismatch
end

field-type-mismatch Warning ​

Assignment to a field doesn't match its @field type:

lua
---@class Config
---@field name string

---@type Config
local cfg = {}
cfg.name = 42 -- field-type-mismatch

assign-type-mismatch Warning ​

Reassignment doesn't match the variable's @type annotation:

lua
---@type string
local x = "hello"
x = 42 -- assign-type-mismatch

generic-constraint-mismatch Warning ​

Generic argument doesn't satisfy the class constraint:

lua
---@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> ...):

lua
---@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:

lua
---@param name string?
function greet(name)
    print(name:upper()) -- need-check-nil
end

Enable in .wowluarc.json: "diagnostics": { "enable": ["need-check-nil"] }

grouped-return-mismatch Warning ​

Return values don't match any declared tuple-union case:

lua
---@return (string, number) | (nil, nil)
function example()
    return "hello", nil -- grouped-return-mismatch
end

Argument diagnostics ​

missing-parameter Warning ​

Required argument not provided:

lua
---@param a number
---@param b number
function add(a, b) end

add(1) -- missing-parameter: b

redundant-parameter Warning ​

Extra arguments beyond what the function accepts:

lua
---@param x number
function single(x) end

single(1, 2) -- redundant-parameter

cannot-call Warning ​

Calling a value whose type is known to be non-callable:

lua
---@type table
local tbl = {}
tbl() -- cannot-call: cannot call a value of type 'table'

---@type number
local n = 5
n() -- cannot-call

invalid-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:

lua
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:

lua
---@return string, number
function getInfo()
    return "hello" -- missing-return-value
end

missing-return Warning ​

Function with @return but no return statement on some paths:

lua
---@param name string?
---@return string
function getName(name)
    if name then
        return name
    end
    -- missing-return (falls off without returning)
end

redundant-return-value Warning ​

Returning more values than declared:

lua
---@return string
function getName()
    return "hello", 42 -- redundant-return-value
end

implicit-nil-return Hint Off by default ​

Bare return in a function with all-optional @return types:

lua
---@param loaded boolean
---@return string?
function maybeName(loaded)
    if not loaded then return end -- implicit-nil-return
    return "name"
end

Global and field diagnostics ​

undefined-global Warning ​

Reference to an unresolved global name:

lua
print(MyUnknownGlobal) -- undefined-global

Suppress 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:

lua
---@class Player
---@field name string

---@type Player
local p = {}
print(p.level) -- undefined-field

A 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:

lua
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 idiom

A 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:

lua
---@class Player
---@field name string

---@type Player
local p = {}
p.level = 10 -- inject-field

create-global Warning ​

Creating a global variable (assignment or function definition without local):

lua
MyGlobal = true -- create-global
function GlobalFunc() end -- create-global

Suppress with globals.write in .wowluarc.json.

missing-fields Warning ​

Missing required fields when constructing a @class table:

lua
---@class Config
---@field name string
---@field debug boolean

---@type Config
local cfg = { name = "test" } -- missing-fields: debug

Annotation diagnostics ​

invalid-class-parent Warning ​

Inheriting from a primitive or literal type instead of a class:

lua
---@class Nums : number      -- invalid-class-parent
---@class Lit : "foo"        -- invalid-class-parent
---@class Union : 1 | 2 | 3  -- invalid-class-parent

undefined-doc-class Warning ​

Undefined parent in @class Foo : Parent:

lua
---@class Child : NonexistentParent -- undefined-doc-class

undefined-doc-name Warning ​

Undefined type name in any annotation:

lua
---@param x NonexistentType -- undefined-doc-name
function foo(x) end

unknown-cast-variable Warning ​

@cast names a variable that isn't in scope, so the cast has no effect:

lua
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:

lua
---@param nme string -- undefined-doc-param (typo: should be 'name')
function greet(name) end

duplicate-doc-param / duplicate-doc-field / duplicate-doc-alias Warning ​

Duplicate annotation declarations.

circle-doc-class Warning ​

Circular inheritance chain:

lua
---@class A : B
---@class B : A -- circle-doc-class

malformed-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:

lua
for i = 10, 1 do -- count-down-loop: needs step -1
    print(i)
end

Style 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:

CodeFires on
unknown-param-typeA parameter, including _ and self, or a reassignment of one
unknown-return-typeA returned value, even in a function with an @return annotation, or a file's annotated return value
unknown-local-typeA local, for, or global variable, or a reassignment of one
unknown-field-typeA 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:

json
{
  "diagnostics": {
    "enable": ["unknown-param-type", "unknown-return-type"]
  }
}