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 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
|
||||
fennel-ls is licensed under the MIT license. See LICENSE for more info.
|
||||
This project also contains files from other projects:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user