lint explanations
These are here mainly for when I get around to mirroring these over to my website. Then we could link a user to an explanation. https://xerool.net/fennel-ls/docs/lints.html#lint-name The descriptions of these are inspired by the Rust Language's Clippy lint descriptions. Check their page out for ways to deal with a large number of lints. https://rust-lang.github.io/rust-clippy/rust-1.86.0/index.html
This commit is contained in:
parent
87439744b8
commit
04fec49059
318
docs/lints.md
Normal file
318
docs/lints.md
Normal file
@ -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.
|
||||||
Loading…
Reference in New Issue
Block a user