Skip to main content
EmmyLua Analyzer provides a powerful static analysis and error detection system. This page explains how to configure diagnostic rules, adjust severity levels, and control diagnostics at the file and line level.

Diagnostic Settings

Enable/Disable Diagnostics

diagnostics.enable
boolean
default:"true"
Master switch to enable or disable the entire diagnostic system.
{
  "diagnostics": {
    "enable": false  // Disable all diagnostics
  }
}
diagnostics.diagnosticInterval
integer
default:"500"
Delay between opening/changing a file and scanning it for errors, in milliseconds. Increase this value if the analyzer is consuming too many resources.
{
  "diagnostics": {
    "diagnosticInterval": 1000  // Wait 1 second before analyzing
  }
}

Disable Specific Diagnostics

diagnostics.disable
string[]
default:"[]"
List of diagnostic codes to disable globally across your project.
{
  "diagnostics": {
    "disable": [
      "undefined-global",
      "unused-local",
      "lowercase-global"
    ]
  }
}

Enable Specific Diagnostics

diagnostics.enables
string[]
default:"[]"
List of diagnostic codes to explicitly enable. Useful when you want to enable diagnostics that are disabled by default.
{
  "diagnostics": {
    "enables": [
      "undefined-field",
      "missing-fields"
    ]
  }
}

Severity Configuration

diagnostics.severity
object
default:"{}"
Map diagnostic codes to custom severity levels. This allows you to make warnings into errors, or errors into hints.Severity Levels:
  • error: Red squiggle, blocks some operations
  • warning: Yellow squiggle
  • information: Blue squiggle
  • hint: Subtle gray squiggle
{
  "diagnostics": {
    "severity": {
      "param-type-mismatch": "error",
      "return-type-mismatch": "error",
      "assign-type-mismatch": "error",
      "unused-local": "warning"
    }
  }
}

Global Variables

Whitelist Global Variables

diagnostics.globals
string[]
default:"[]"
List of global variable names that should not trigger undefined-global warnings.
{
  "diagnostics": {
    "globals": ["vim", "love", "ngx", "jit", "bit"]
  }
}

Global Variable Patterns

diagnostics.globalsRegex
string[]
default:"[]"
List of regular expression patterns for global variables. Useful for matching multiple globals with similar names.
{
  "diagnostics": {
    "globalsRegex": [
      "^GLOBAL_.*",      // GLOBAL_CONFIG, GLOBAL_DEBUG, etc.
      "^_G\\..*",          // _G.anything
      "^[A-Z_]+$"        // Any uppercase with underscores
    ]
  }
}

Available Diagnostic Codes

Syntax and Parsing

Syntax errors in Lua code.
-- Triggers syntax-error
function foo(
  -- Missing closing parenthesis
Syntax errors in EmmyLua documentation comments.
---@param invalid syntax here
function foo() end

Type Checking

Referenced type is not defined.
---@type UndefinedType
local var
Parameter type doesn’t match the expected type.
---@param name string
function greet(name)
  print(name)
end

greet(123)  -- param-type-mismatch: expected string, got number
Return value type doesn’t match the declared return type.
---@return string
function getName()
  return 123  -- return-type-mismatch: expected string, got number
end
Assignment type doesn’t match the variable’s type.
---@type string
local name = 123  -- assign-type-mismatch

Function Analysis

Function is missing a return statement but declares a return type.
---@return string
function getName()
  -- missing-return: no return statement
end
Return statement is missing required values.
---@return string, number
function getData()
  return "hello"  -- missing-return-value: expected 2 values, got 1
end
Return statement has more values than declared.
---@return string
function getName()
  return "hello", 123  -- redundant-return-value
end
Function call is missing required parameters.
---@param name string
---@param age number
function register(name, age) end

register("Alice")  -- missing-parameter: age required
Function call has more parameters than expected.
---@param name string
function greet(name) end

greet("Alice", "extra")  -- redundant-parameter

Variables and Scope

Global variable is used but not defined.
print(undefinedVariable)  -- undefined-global
Field access on undefined property.
---@class User
---@field name string
local user = {}

print(user.email)  -- undefined-field
Variable or function is defined but never used.
local unusedVar = 10  -- unused
Local variable is redefined in the same scope.
local x = 1
local x = 2  -- redefined-local
Attempting to reassign a constant local variable.
---@const
local MAX_SIZE = 100
MAX_SIZE = 200  -- local-const-reassign
Iterator variable is being reassigned in the loop body.
for i = 1, 10 do
  i = i + 1  -- iter-variable-reassign
end

Classes and Fields

Table literal is missing required fields from its type.
---@class Config
---@field host string
---@field port number

---@type Config
local config = {  -- missing-fields: port required
  host = "localhost"
}
Field is being injected into a class that doesn’t declare it.
---@class User
---@field name string

local user = { name = "Alice" }
user.age = 25  -- inject-field
Type is defined multiple times.
---@alias Status string
---@alias Status string  -- duplicate-type
Circular inheritance in class definitions.
---@class A : B
---@class B : A  -- circle-doc-class

Code Quality

Code that can never be executed.
function foo()
  return true
  print("never runs")  -- unreachable-code
end
Using a deprecated symbol.
---@deprecated Use newFunction instead
function oldFunction() end

oldFunction()  -- deprecated
Code doesn’t follow configured style guidelines.
Field is assigned multiple times in the same table literal.
local config = {
  debug = true,
  debug = false  -- duplicate-set-field
}
Array index is used multiple times in the same table literal.
local arr = {
  [1] = "a",
  [1] = "b"  -- duplicate-index
}
Module is required multiple times.
local json = require("json")
local json2 = require("json")  -- duplicate-require

Advanced

Value might be nil and should be checked before use.
---@param value string?
function process(value)
  print(value:upper())  -- need-check-nil
end
Accessing a private or protected member from outside the class.
---@class MyClass
---@field private _internal string

local obj = MyClass.new()
print(obj._internal)  -- access-invisible
Function return values are being discarded when they should be used.
---@return string
---@nodiscard
function getValue() return "value" end

getValue()  -- discard-returns
Type cast is invalid.
---@type string
local str = "hello"

---@cast str number  -- cast-type-mismatch
Generic type constraint is violated.
---@generic T : string
---@param value T
function process(value) end

process(123)  -- generic-constraint-mismatch

File-Level Diagnostic Control

Use the ---@diagnostic annotation to control diagnostics at the file, block, or line level.

Syntax

---@diagnostic <action>: <diagnostic_name>[, <diagnostic_name>...]
Actions:
  • disable: Disable diagnostics until re-enabled
  • disable-next-line: Disable for the next line only
  • disable-line: Disable for the current line only
  • enable: Re-enable previously disabled diagnostics

Line-Level Control

-- Disable specific diagnostic for the next line
---@diagnostic disable-next-line: undefined-global
print(someUndefinedVariable)

Block-Level Control

-- Disable for a block of code
do
  ---@diagnostic disable: undefined-global
  someGlobalVar = "this is OK"
  anotherGlobalVar = 42
  ---@diagnostic enable: undefined-global
end

-- This line will show warnings again
thirdGlobal = "not OK"

File-Level Control

-- At the top of the file, disable diagnostics for the entire file
---@diagnostic disable: undefined-global, lowercase-global

-- All code below will have these diagnostics disabled
GLOBAL_CONFIG = {
  debug = true
}

function setup()
  ANOTHER_GLOBAL = true
end

Common Configuration Patterns

Strict Type Checking

{
  "diagnostics": {
    "severity": {
      "param-type-mismatch": "error",
      "return-type-mismatch": "error",
      "assign-type-mismatch": "error",
      "missing-return": "error",
      "need-check-nil": "error"
    }
  },
  "strict": {
    "typeCall": true,
    "arrayIndex": true
  }
}

Game Development (Lenient)

{
  "diagnostics": {
    "globals": ["love"],
    "disable": [
      "lowercase-global",
      "undefined-field"
    ],
    "severity": {
      "param-type-mismatch": "hint",
      "unused-local": "hint"
    }
  }
}

Library Development

{
  "diagnostics": {
    "disable": ["unused-local"],
    "severity": {
      "missing-return": "error",
      "param-type-mismatch": "error",
      "incomplete-signature-doc": "warning",
      "missing-global-doc": "warning"
    }
  }
}

Legacy Codebase

{
  "diagnostics": {
    "disable": [
      "undefined-global",
      "lowercase-global",
      "undefined-field"
    ],
    "severity": {
      "param-type-mismatch": "hint",
      "return-type-mismatch": "hint"
    }
  }
}

Best Practices

Begin with the default diagnostic settings and only disable rules that cause too many false positives in your specific codebase.
Instead of disabling diagnostics globally, use ---@diagnostic annotations for specific files that need exceptions (e.g., test files, compatibility layers).
Don’t disable diagnostics unless absolutely necessary. Consider changing severity to hint instead of disabling completely.
Add comments in your .emmyrc.json explaining why certain diagnostics are disabled or have custom severity.
Prefer declaring globals with ---@type annotations in your code rather than adding them to the global whitelist.

Build docs developers (and LLMs) love