Linting ======= Luau comes with a set of linting passes, that help make sure that the code is correct and consistent. Unlike the type checker, that models the behavior of the code thoroughly and points toward type mismatches that are likely to result in runtime errors, the linter is more opinionated and produces warnings that can often be safely ignored, although it's recommended to keep the code clean of the warnings. Linter produces many different types of warnings; many of these are enabled by default, and can be suppressed by declaring `--!nolint NAME` at the top of the file. In dire situations `--!nolint` at the top of the file can be used to completely disable all warnings (note that the type checker is still active, and requires a separate `--!nocheck` declaration). The rest of this page documents all warnings produced by the linter; each warning has a name and a numeric code, the latter is used when displaying warnings. UnknownGlobal (1) === By default, variables in Luau are global (this is inherited from Lua 5.x and can't be changed because of backwards compatibility). This means that typos in identifiers are invisible to the parser, and often break at runtime. For this reason, the linter considers all globals that aren't part of the builtin global table and aren't explicitly defined in the script "unknown": ```lua local displayName = "Roblox" -- Unknown global 'displaName' print(displaName) ``` Note that injecting globals via `setfenv` can produce this warning in correct code; global injection is incompatible with type checking and has performance implications so we recommend against it and in favor of using `require` with correctly scoped identifiers. DeprecatedGlobal (2) === Some global names exist for compatibility but their use is discouraged. This mostly affects globals introduced by Roblox, and since they can have problematic behavior or can break in the future, this warning highlights their uses: ```lua -- Global 'ypcall' is deprecated, use 'pcall' instead ypcall(function() print("hello") end) ``` GlobalUsedAsLocal (3) === The UnknownGlobal lint can catch typos in globals that are read, but can't catch them in globals that are assigned to. Because of this, and to discourage the use of globals in general, linter detects cases when a global is only used in one function and can be safely converted to a local variable. Note that in some cases this requires declaring the local variable in the beginning of the function instead of where it's being assigned to. ```lua local function testFunc(a) if a < 5 then -- Global 'b' is only used in the enclosing function; consider changing it to local b = 1 else b = 2 end print(b) end ``` LocalShadow (4) === In Luau, it is valid to shadow locals and globals with a local variable, including doing it in the same function. This can result in subtle bugs, since the shadowing may not be obvious to the reader. This warning detects cases where local variables shadow other local variables in the same function, or global variables used in the script; for more cases of detected shadowing see `LocalShadowPedantic`. ```lua local function foo() for i=1,10 do -- Variable 'i' shadows previous declaration at line 2 for i=1,10 do print(i) end end end ``` SameLineStatement (5) === Luau doesn't require the use of semicolons and doesn't automatically insert them at line breaks. When used wisely this results in code that is easy to read and understand, however it can cause subtle issues and hard to understand code when abused by using many different statements on the same line. This warning highlights cases where code should either be broken into multiple lines, or use `;` as a visual guide. ```lua -- A new statement is on the same line; add semi-colon on previous statement to silence if b < 0 then local a = b + 1 print(a, b) end ``` MultiLineStatement (6) === An opposite problem is having statements that span multiple lines. This is good for readability when the code is indented properly, but when it's not it results in code that's hard to understand, as its easy to confuse the next line for a separate statement. ```lua -- Statement spans multiple lines; use indentation to silence print(math.max(1, math.min(2, 3))) ``` LocalUnused (7) === This warning is one of the few warnings that highlight unused variables. Local variable declarations that aren't used may indicate a bug in the code (for example, there could be a typo in the code that uses the wrong variable) or redundant code that is no longer necessary (for example, calling a function to get its result and never using this result). This warning warns about locals that aren't used; if the locals are not used intentionally they can be prefixed with `_` to silence the warning: ```lua local x = 1 -- Variable 'y' is never used; prefix with '_' to silence local y = 2 print(x, x) ``` FunctionUnused (8) === While unused local variables could be useful for debugging, unused functions usually contain dead code that was meant to be removed. Unused functions clutter code, can be a result of typos similar to local variables, and can mislead the reader into thinking that some functionality is supported. ```lua -- Function 'bar' is never used local function bar() end local function foo() end return foo() ``` ImportUnused (9) === In Luau, there's no first-class module system that's part of the syntax, but `require` function acts as an import statement. When a local is initialized with a `require` result, and the local is unused, this is classified as "unused import". Removing unused imports improves code quality because it makes it obvious what the dependencies of the code are: ```lua -- Import 'Roact' is never used local Roact = require(game.Packages.Roact) ``` BuiltinGlobalWrite (10) === While the sandboxing model of Luau prevents overwriting built-in globals such as `table` for the entire program, it's still valid to reassign these globals - this results in "global shadowing", where the script's global table contains a custom version of `table` after writing to it. This is problematic because it disables some optimizations, and can result in misleading code. When shadowing built-in globals, use locals instead. ```lua -- Built-in global 'math' is overwritten here; consider using a local or changing the name math = {} ``` PlaceholderRead (11) === `_` variable name is commonly used as a placeholder to discard function results. The linter follows this convention and doesn't warn about the use of `_` in various cases where a different name would cause a warning otherwise. To make sure the placeholder is only used to write values to it, this warning detects the cases where it's read instead: ``` local _ = 5 -- Placeholder value '_' is read here; consider using a named variable return _ ``` UnreachableCode (12) === In some cases the linter can detect code that is never executed, because all execution paths through the function exit the function or the loop before reaching it. Such code is misleading because it's not always obvious to the reader that it never runs, and as such it should be removed. ```lua function cbrt(v) if v >= 0 then return v ^ 1/3 else error('cbrt expects a non-negative argument') end -- Unreachable code (previous statement always returns) return 0 end ``` UnknownType (13) === Luau provides several functions to get the value type as a string (`type`, `typeof`), and some Roblox APIs expose class names through string arguments (`Instance.new`). This warning detects incorrect use of the type names by checking the string literals used in type comparisons and function calls. ```lua -- Unknown type 'String' (expected primitive type) if type(v) == "String" then print("v is a string") end ``` ForRange (14) === When using a numeric for, it's possible to make various mistakes when specifying the for bounds. For example, to iterate through the table backwards, it's important to specify the negative step size. This warning detects several cases where the numeric for only runs for 0 or 1 iterations, or when the step doesn't divide the size of the range evenly. ```lua -- For loop should iterate backwards; did you forget to specify -1 as step? for i=#t,1 do end ``` UnbalancedAssignment (15) === Assignment statements and local variable declarations in Luau support multiple variables on the left side and multiple values on the right side. The number of values doesn't need to match; when the right side has more values, the extra values are discarded, and then the left side has more variables the extra variables are set to `nil`. However, this can result in subtle bugs where a value is omitted mistakenly. This warning warns about cases like this; if the last expression on the right hand side returns multiple values, the warning is not emitted. ```lua -- Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence local x, y, z = 1, 2 ``` ImplicitReturn (16) === In Luau, there's a subtle difference between returning no values from a function and returning `nil`. In many contexts these are equivalent, but when the results are passed to a variadic function (perhaps implicitly), the difference can be observed - for example, `print(foo())` prints nothing if `foo` returns no values, and `nil` if it returns `nil`. To help write code that has consistent behavior, linter warns about cases when a function implicitly returns no values, if there are cases when it explicitly returns a result. For code like this it's recommended to use explicit `return` or `return nil` at the end of the function (these have different semantics, so the correct version to use depends on the function): ```lua local function find(t, expected) for k,v in pairs(t) do if k == expected then return v end end -- Function 'find' can implicitly return no values even though there's an explicit return at line 4; add explicit return to silence end ``` LocalShadowPedantic (17) === This warning extends `LocalShadow` by also warning about cases where a local variable shadows a local variable with the same name from a parent function, or when it shadows a builtin global. This warning tends to be noisy and as such is disabled by default.