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