From 0eb36fd40bc47c58b9c62612f1d9e473aa899d72 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 11 Jun 2020 22:17:38 -0700 Subject: [PATCH] docs: Add linter docs This describes all warnings, including ones that aren't enabled in Studio by default yet. --- docs/lint.md | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/docs/lint.md b/docs/lint.md index e58a660..299e9fc 100644 --- a/docs/lint.md +++ b/docs/lint.md @@ -1,2 +1,220 @@ 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.