diff --git a/docs/lints.md b/docs/lints.md new file mode 100644 index 0000000..31190db --- /dev/null +++ b/docs/lints.md @@ -0,0 +1,318 @@ +# unused-definition +## What it does +Marks bindings that aren't read. Completely overwriting a value doesn't count +as reading it. A variable that starts or ends with an `_` will not trigger this +lint. Use this to suppress the lint. + +## Why is this bad? +Unused definitions can lead to bugs and make code harder to understand. Either +remove the binding, or add an `_` to the variable name. + +## Example +```fnl +(local value 100) +(set value 10) +``` + +Instead, use the value, remove it, or add `_` to the variable name. +```fnl +(local value 100) +(set value 10) +;; use the value +(print value) +``` + +## Known limitations +Fennel's pattern matching macros also check for leading `_` for symbol names. +This means that adding an `_` can change the semantics of the code. In this +situation, add the `_` to the end of the symbol to disable the lint without +changing the pattern's meaning. +```fnl +(match [10 nil] + ;; pattern works as intended, but triggers the lint + [a b] (print a "unintended") + _ (print "unintended")) + +(match [10 nil] + ;; pattern matches when we don't want it to + [a _b] (print a "unintended") + _ (print "unintended")) + +(match [10 nil] + ;; works as intended and doesn't trigger lint + [a b_] (print a "unintended") + _ (print "unintended")) +``` + +Think of it this way: +`identifier` - must be used, and should be non-`nil` +`?identifier` - must be used, and can be `nil` +`_identifier` - may be unused, and can be `nil` +`identifer_` - may be unused, but should be non-`nil` + +# unknown-module-field +## What it does +Looks for module fields that can't be statically determined to exist. This only +triggers if the module is found, but there's no definition of the field inside +of the module. + +## Why is this bad? +This is probably a typo, or a missing function in the module. + +## Example +```fnl +;;; in `a.fnl` +{: print} + +;;; in `b.fnl` +(local a (require :a)) +(a.printtt 100) +``` +Instead, use: +```fnl +;;; in `b.fnl` +(local a (require :a)) +(a.print 100) ; typo fixed +``` + +## Known limitations +Fennel-ls doesn't have a full type system, so we're not able to check every +multisym statically, but as a heuristic, usually modules are able to be +evaluated statically. If you have a module that can't be figured out, please +let us know on the bug tracker. + +# unnecessary-method +## What it does +Checks for unnecessary uses of the `:` method call syntax when a simple multisym +would work. + +## Why is this bad? +Using the method call syntax unnecessarily adds complexity and can make code +harder to understand. + +## Example +```fnl +(: alien :shoot-laser {:x 10 :y 20}) +``` + +Instead, use: +```fnl +(alien:shoot-laser {:x 10 :y 20}) +``` + +# unnecessary-tset +## What it does +Identifies unnecessary uses of `tset` when a `set` with a multisym would be clearer. + +## Why is this bad? +Using `tset` makes the code more verbose and harder to read when a simpler +alternative exists. + +## Example +```fnl +(tset alien :health 1337) +``` + +Instead, use: +```fnl +(set alien.health 1337) +``` + +# unnecessary-do-values +## What it does +Warns about unnecessary `do` or `values` forms that only contain a single expression. + +## Why is this bad? +Extra `do` or `values` forms without multiple expressions add syntactic noise. + +## Example +```fnl +(do (print "hello")) + +(values (+ 1 2)) +``` + +Instead, use: +```fnl +(print "hello") + +(+ 1 2) +``` + +# redundant-do +## What it does +Identifies redundant `do` blocks within implicit do forms like `fn`, `let`, etc. + +## Why is this bad? +Redundant `do` blocks add unnecessary nesting and make code harder to read. + +## Example +```fnl +(fn [] (do + (print "first") + (print "second"))) +``` + +Instead, use: +```fnl +(fn [] + (print "first") + (print "second")) +``` + +# bad-unpack +## What it does +Warns when `unpack` or `table.unpack` is used with operators that aren't +variadic at runtime. + +## Why is this bad? +Fennel operators like `+`, `*`, etc. look like they should work with `unpack`, +but they don't actually accept a variable number of arguments at runtime. + +## Example +```fnl +(+ 1 (unpack [2 3 4])) ; Only adds 1 and 2 +(.. (unpack ["a" "b" "c"])) ; Only concatenates "a" +``` + +Instead, use: +```fnl +;; For concatenation: +(table.concat ["a" "b" "c"]) + +;; For other operators, use a loop: +(accumulate [sum 0 _ n (ipairs [1 2 3 4])] + (+ sum n)) +``` + +# var-never-set +## What it does +Identifies variables declared with `var` that are never modified with `set`. + +## Why is this bad? +If a `var` is never modified, it should be declared with `local` or `let` instead +for clarity. + +## Example +```fnl +(var x 10) +(print x) +``` + +Instead, use: +```fnl +(local x 10) +(print x) +``` + +# op-with-no-arguments +## What it does +Warns when an operator is called with no arguments, which can be replaced with +an identity value. + +## Why is this bad? +Calling operators with no arguments is less clear than using the identity value +directly. + +## Example +```fnl +(+) ; Returns 0 +(*) ; Returns 1 +(..) ; Returns "" +``` + +Instead, use: +```fnl +0 +1 +"" +``` + +## Note +This lint isn't actually very useful. + +# no-decreasing-comparison (off by default) +## What it does +Suggests using increasing comparison operators (`<`, `<=`) instead of decreasing ones (`>`, `>=`). + +## Why is this bad? +Consistency in comparison direction makes code more readable and maintainable, +especially in languages with lisp syntax. You can think of `<` as a function that +tests if the arguments are in sorted order. + +## Example +```fnl +(> a b) +(>= x y z) +``` + +Instead, use: +```fnl +(< b a) +(<= z y x) +``` + +# match-should-case +## What it does +Suggests using `case` instead of `match` when the meaning would not be altered. + +## Why is this bad? +The `match` macro's meaning depends on the local variables in scope. When a +`match` call doesn't use the local variables, it can be replaced with the +`case` form. + +## Example +```fnl +(match value + 10 "ten" + 20 "twenty" + _ "other") +``` + +Instead, use: +```fnl +(case value + 10 "ten" + 20 "twenty" + _ "other") +``` + +# multival-in-middle-of-call +## What it does +Warns when multiple values from `values` or `unpack` are used in a non-final +position of a function call, where only the first value will be used. + +## Why is this bad? +In Fennel (and Lua), multiple values are only preserved when they appear in the +final position of a function call. Using them elsewhere results in only the +first value being used. This is likely not what was intended, since the use of +`values` or `unpack` seems to imply that the code is interested in handling +multivals instead of discarding them. + +## Example +```fnl +(print (values 1 2 3) 4) ; confusingly prints "1 4" +``` + +Instead, use: +```fnl +;; Either put the multival at the end: +(print 4 (values 1 2 3)) + +;; Change your logic to explicitly capture the values +(let [(a b c) (values 1 2 3)] + (print a b c 4) + +;; Use `do` to make it clear that you intend to ignore the extra values +(print (do 2 3 1) 4)) +``` + +## Limitations +It doesn't make sense to flag *all* places where a multival is discarded, because +discarding extra values is common in Lua. For example, in the standard library +of Lua, `string.gsub` and `require` actually return two results, even though +most of the time, only the first one is what's wanted. + +This lint specifically flags discarding multivals from `values` and `unpack`, +instead of flagging all discards, because these forms indicate that the user +*intends* for something to happen with multivals. \ No newline at end of file