Writing an Elm linter In Elm
Jeroen Engels (Yeuroune)
ESLint
ESLint
Writing a linter teaches you about the language
Let's build an Elm Linter!
foobar : String
-> List (Html Msg)
-> Html Msg
foobar title children =
li []
[ pre [] [ text title ]
, ul [] children
]
Code is hard to analyze
(a + b) / 2
Integer 2
BinaryExpression
operator +
Variable a
Variable b
BinaryExpression
operator /
AST - Abstract Syntax Tree
elm-ast
- https://github.com/Bogdanp/elm-ast
- Still written for Elm 0.17...
ESLint AST
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "BinaryExpression",
"left": {
"type": "BinaryExpression",
"left": {
"type": "Identifier",
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"name": "b"
}
},
"operator": "/",
"right": {
"type": "Literal",
"value": 2,
"raw": "2"
}
}
}
]
}
elm-ast
Visiting the tree
Visiting the tree
Integer 2
BinaryExpression
operator +
Variable a
Variable b
BinaryExpression
operator /
- Collect information as we visit the tree
- Report errors along the way
Visiting the tree
How to write a rule
expressionFn : Context
-> Direction Expression
-> ( List Error, Context )
expressionFn ctx node =
case node of
Enter (SomeNodeType) ->
if condition then
( [ createError "message" ], ctx )
else
( [], { ctx | data = ctx.data ++ foobar } )
_ ->
( [], ctx )
module SomeRule exposing (rule)
import Lint exposing (lint, doNothing)
import Types exposing (LintRule, Error, Direction(..))
type alias Context =
{ data: List String
}
rule : String -> List Error
rule input =
lint input implementation
implementation : LintRule Context
implementation =
{ statementFn = doNothing
, typeFn = doNothing
, expressionFn = expressionFn
, moduleEndFn = (\ctx -> ( [], ctx ))
, initialContext = Context []
}
Why so many functions?
-- Could not write this
type Node
= Statement
| Expression
| Type
fn : Context
-> Direction Node
-> ( List Error, Context )
fn ctx node =
( [], ctx )
Example: No Debug
- Aim: Forbid all uses of `Debug` in your code
foo data =
case Debug.log "data" data of
_ -> []
Example:
No unannotated functions
- Aim: Force all top-level functions to have a type annotation
elm-lint
elm-ast shortcomings
- No information of *where* the error is in the code,
elm-ast does not give the information :/ - Elm syntax is not 100% supported, there are bugs
elm-lint
-
4 rules implemented
- No Debug
- No Unannotated Functions
- No Exposing Everything `module Main exposing (..)`
- No Unused Variables
elm-lint wannabe rules
- Duplicated imports `import Regex ; Import Regex`
- No importing everything `import Regex exposing (..)`: confusing!
- `a |> List.map f |> List.map g` --> `a |> List.map (f >> g)`
- elm-html: No putting a `textarea`tag inside a `p` tag
elm-lint wannabe rules
- Missing port in elm-test
- No lingering Test.filter in elm-test
main : Test.Runner.Node.TestProgram
main =
run emit all
port emit : ( String, Value ) -> Cmd msg
all : Test
all =
describe "String.reverse"
[ test "has no effect on a palindrome" testGoesHere
, test "reverses a known string" anotherTest
, fuzz string "restores the original string if you run it again" oneMore
]
|> Test.filter (String.contains "original")
elm-lint wannabe rules
- Handling things the compiler does not
- `x = x + 1` caused runtime errors in v0.17
- Was not fixed for months until v0.18 came out
- Easily detectable
Clearly less useful than ESLint
- Style: elm-format handles all styles and is widely adopted
- The compiler handles (almost) all errors
- JavaScript desperately needs a linter. Elm less so
elm-lint
By Jeroen Engels
elm-lint
- 1,122