# What we've learnt using Elm

Jeroen Engels

Key learnings from using Elm for several months

## 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 /``````

## 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"
}
}
}
]
}``````

## Visiting the tree

``Integer 2``
``````BinaryExpression
operator +``````
``Variable a``
``Variable b``
``````BinaryExpression
operator /``````
• Collect information as we visit 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
_ -> []``````

## No unannotated functions

• Aim: Force all top-level functions to have a type annotation

## 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