Lints
Luwu expands Luau's built-in lint system with more than 40 new lints for Luau and Luwu code. They help you write more idiomatic and performant code and catch mistakes the type checker can't see.
Lint control
Most lints are on by default. Turn one off for a file with --!nolint Name, or for one statement with @[nolint(Name)] above it. Turn on a lint that's off by default with --!lint Name:
--!lint ConstLocal
local total = 0
for _, price in { 4, 8, 15 } do
total += price
end
print(total)ConstLocal flags any local that's never reassigned and could be const. Because total is reassigned here, nothing is reported.
All(not a lint): the name for every lint, as in--!nolint Allor@[nolint(All)]to turn them all off on purpose.BareNolint:--!nolintor@nolintwith no lint names: it silences everything and is probably an accident.
Optimization hints
OptimizationHint is the parent of every hint below: code the compiler could optimize better if it were written differently. Turning it off turns them all off.
LoopConcat: growing a string with..inside a loop copies the whole string every iteration. The lint reports the cost for the loop nest (O(n²), O(n⁴), ...).InefficientTableInsert:table.insert(t, 1, v)in a loop shifts every element on each insert.InefficientTableRemove:table.remove(t, 1)in a loop shifts every element on each removal.MethodsNotInlined: code that keeps the compiler from proving an object's class and inlining its method calls:if cat thenon a class-typed binding is only a nil check.if class.isinstance(cat, Cat) thenproves the class for the branch.assert(cat)is only a nil check.assert(class.isinstance(cat, Cat))proves the class for the rest of the block.- several method calls through the same field (
self.pos:add(...)). A field can't be proven in place: hoist it into a local and prove that once.
FloorDivision:math.floor(a / b)is a division plus a call, whilea // bis one instruction.FenvDeoptimization: callinggetfenvorsetfenvturns off optimizations for the entire module.
Loops
RemoveWhileIterating:table.remove(t, i)while iteratingtforwards byiskips the element after each removal.LoopVariableWrite: a loop variable that's written to but never read afterwards.IteratedTableWrite: adding or changing keys other than the current one while iterating a table can skip or repeat entries.ForeverLoop: awhileorrepeatloop whose condition nothing inside the loop can change.UselessLoop: a loop where every path through itbreaks (or errors) on the first iteration. Loops that leave byreturnaren't reported, and neither is aforloop that leaves on its first iteration after using a loop variable.
Values
StringIndexZero: string functions called with index 0 (string.byte(s, 0),string.sub(s, 0, n)). Strings start at 1.NewValueComparison: comparing against a table or function literal (x == {}) is always false.TableTruthiness: testing a table's truthiness to check whether it's empty (if t then). Tables are always truthy.DiscardedResult: a builtin function, or a function marked@nodiscard, whose return values weren't used or explicitly discarded with_.NilNoneComparison: comparing withnilwhere onlynoneis possible, or the reverse.FloatIndex: an index computed with/likexs[#xs / 2]is a float.VarargCast:f(... :: T)passes only the first value.LuaAndOr:a and b or conly acts likeif a then b else cwhenbis truthy.SelfAssignment:x = xdoes nothing.DeadStore: a computed value that's overwritten before anything reads it.DeclareMismatch:declare x: Tgiving a global from the loaded definitions a different type.
Expressive control flow
ReturnOnNextLine: areturnin adoexpression whose value starts on the next line and is easy to misread.OrContinue:x or continuein a module with a variable namedcontinue. There it reads the variable instead of skipping to the next iteration.OrReturnComma:f(x or return a, b)passesbas the next argument, not as a second return value.BindingSwallowsCondition: anandthat's part of the bound value, as inif local x = f() and x > 1.
Names
KeywordShadow: a binding named after a contextual keyword (continue,class,implements,declare,export,const).BuiltinShadow: a binding that hides a builtin global (type,typeof,table, ...).ReturnSelf: a trait method returningselftyped as the trait, instead ofSelf.
Idiomatic Luwu
ConstLocal: off by default. Alocalthat's never reassigned and could beconst.LuaIterators:pairs(t)andipairs(t)calls that generalized iteration replaces.PairsandIpairsturn off each one separately.LuaGuard:if not x then return endon one line that Luwu can write asx or return.LuwuOnlyApi: a Luwu-only library function, liketable.drop, used in a.luaufile.
Style
Style is the group of layout lints. Turning it off turns all of them off, including ConstLocal, SameLineStatement and MultiLineStatement.
OneLineStatement:if x then y end,while c do f() endorfunction f() return 1 endwritten on one line.OneLineGuard:if not x then return endon one line in a Luau file.InlineOrLeave:x or returnending a line much longer than the lines around it.MisleadingIndentation: adoexpression's block reaching lines indented like the code after it.