Instructions for creating lint rules
This commit is contained in:
parent
647421f21b
commit
5c19cb3a83
93
Adding-a-Lint-Rule.md
Normal file
93
Adding-a-Lint-Rule.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Creating a new lint
|
||||||
|
To start, you can set up all the plumbing:
|
||||||
|
|
||||||
|
1. Go into `src/fennel-ls/diagnostics.fnl` and create a new function.
|
||||||
|
2. At the bottom of `src/fennel-ls/diagnostics.fnl`, add an if statement in the
|
||||||
|
`check` function.
|
||||||
|
* Choose which `each` loop to put your function in, so your lint can be
|
||||||
|
applied to right thing.
|
||||||
|
* add `(if checks.<your-check> (table.insert diagnostics (<your-check> self file <the rest of the args>)))`
|
||||||
|
3. Enable your lint! In `src/fennel-ls/state.fnl`, find the
|
||||||
|
`default-configuration` variable, and turn your check on by default.
|
||||||
|
|
||||||
|
# Writing your lint
|
||||||
|
Now, the fun part: writing your lint function.
|
||||||
|
|
||||||
|
The goal is to check whether the given arguments should emit a warning, and
|
||||||
|
what message to show. The current loops in `check` go over every:
|
||||||
|
* definition (Every time a new variable is bound)
|
||||||
|
* call (Every time the user calls a function or a special. Macros don't count.)
|
||||||
|
|
||||||
|
More loops might have been added since I wrote this document.
|
||||||
|
|
||||||
|
## Input arguments
|
||||||
|
All lints give you `self` and `file`. They're mostly useful to pass to other
|
||||||
|
functions.
|
||||||
|
* `self` is the table that represents the language server. It carries metadata
|
||||||
|
and stuff around. You probably don't need to use it directly.
|
||||||
|
* `file` is an object that represents a .fnl file. It has some useful fields.
|
||||||
|
Check out what fields it has by looking at the end of `compiler.fnl`. The
|
||||||
|
most important one for lints is probably `lexical`. `file.lexical` stores the
|
||||||
|
value `true` for every single list, table, or symbol that appears in the
|
||||||
|
original file AST. If your lint is supposed to apply to hand written code,
|
||||||
|
but doesn't really apply to generated code, you should make sure that the AST
|
||||||
|
you're checking is inside of file.lexical.
|
||||||
|
|
||||||
|
The next arguments depend on which loop the lint is in:
|
||||||
|
### If your lint is linting definitions:
|
||||||
|
* `symbol` is the symbol being bound. It is just a regular fennel sym.
|
||||||
|
* `definition` is a table full of information about what is being bound:
|
||||||
|
* `definition.binding` is the symbol again.
|
||||||
|
* `definition.definition`, if present, is the expression that we're
|
||||||
|
evaluating.
|
||||||
|
* `definition.referenced-by` is a list of "reference" object things.
|
||||||
|
* `definition.keys`, if present, tells you what part of the definition is
|
||||||
|
getting bound to `symbol`. It might be nil.
|
||||||
|
* `definition.multival` tells you which value of the definition is getting
|
||||||
|
bound to `symbol`, assuming `definition.definition` produces multiple
|
||||||
|
values.
|
||||||
|
* `definition.var?` is a boolean, which tells if the `symbol` is introduced
|
||||||
|
as a variable.
|
||||||
|
|
||||||
|
For example, if I write the code `(var x 1000)`, the definition will be:
|
||||||
|
```fnl
|
||||||
|
{:definition 1000 :binding `x :var? true}
|
||||||
|
```
|
||||||
|
If I write the code `(let [(x {:foo {:bar y}}) (my-expression)] x.myfield)`,
|
||||||
|
the definitions will be:
|
||||||
|
```fnl
|
||||||
|
;; for x
|
||||||
|
{:definition `(my-expression)
|
||||||
|
:binding `x
|
||||||
|
:multival 1
|
||||||
|
:referenced-by {:symbol `x.field :target @1 :ref-type "read"}}
|
||||||
|
;; for y
|
||||||
|
{:definition `(my-expression) :binding `y :multival 2 :keys [:foo :bar]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### If your lint is linting calls (to functions or specials, not macros)
|
||||||
|
`head` is the symbol that is being called. It is the same as `(. call 1)`.
|
||||||
|
`call` is the list that represents the call.
|
||||||
|
|
||||||
|
## Output:
|
||||||
|
Return `nil` if there's nothing to report, return a diagnostic object with your
|
||||||
|
lint. The return value should have these fields:
|
||||||
|
|
||||||
|
* `range`: make these with `message.ast->range` to get the range for a list or
|
||||||
|
symbol or table, or with `message.multisym->range` to get the range of a
|
||||||
|
specific segment of a multisym. Try to report specifically on which piece of
|
||||||
|
AST is wrong. If its the entire call, give the range of the call. If its a
|
||||||
|
specific argument, give the range of that argument if possible, and the call
|
||||||
|
if not. Remember that we can't get the range of things like numbers and
|
||||||
|
strings, because they don't have tracking info.
|
||||||
|
* `message`: this is the message your lint will produce. Try to make it
|
||||||
|
specific and helpful as possible.
|
||||||
|
* `severity`: hardcode this to `message.severity.WARN`. ERROR is for compiler
|
||||||
|
errors, and WARN is for lints.
|
||||||
|
* `code`: Please use a new number counting up from 301 for each lint. The codes
|
||||||
|
are nice to have so that the message of the lint can change without breaking
|
||||||
|
tests that only check for the presence or absence of a lint.
|
||||||
|
* `codeDescription`: Use the name of your function.
|
||||||
|
|
||||||
|
## Testing:
|
||||||
|
I will think about this later. :)
|
||||||
@ -73,6 +73,10 @@ Your editor can send these settings using one of these two methods:
|
|||||||
* The client sends an `initialize` request with the structure `{initializationOptions: {"fennel-ls": {...}}, ...}`
|
* The client sends an `initialize` request with the structure `{initializationOptions: {"fennel-ls": {...}}, ...}`
|
||||||
* The client sends a `workspace/didChangeConfiguration` notfication containing the field `{settings: {"fennel-ls": {YOUR_SETTINGS}}}`
|
* The client sends a `workspace/didChangeConfiguration` notfication containing the field `{settings: {"fennel-ls": {YOUR_SETTINGS}}}`
|
||||||
|
|
||||||
|
## Adding a lint
|
||||||
|
You can't load external lint rules with fennel-ls, but I would love to receive patches that add new lint rules!
|
||||||
|
[Instructions to add a lint.](Adding-a-Lint-Rule.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
fennel-ls is licensed under the MIT license. See LICENSE for more info.
|
fennel-ls is licensed under the MIT license. See LICENSE for more info.
|
||||||
This project also contains files from other projects:
|
This project also contains files from other projects:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user