elm-lint lints Elm source code, to add additional guarantees to your project.

elm-lint do?elm-lint analyzes your Elm source code, and tries to recognize patterns that may be considered harmful or can be improved upon.
If you are familiar with ESLint from JavaScript, this is pretty much the same idea.
The idea is to improve your Elm source code base, after it passes compilation and elm-format has been run on it.
You can configure your project to be analyzed by different "rules". You can find some in the Elm packages, or you can write your own rules to enforce rules specific to your project or team. A few use-cases:
;).elm-lint in their project.When solving a problem, a good API is a usually a better solution than writing a linter rule. But in some cases, even if you've written a good API, nothing prevents teammates or yourself from falling in the same unwanted patterns as before, especially when dealing with primitive values or constructs.
When introducing elm-lint or new rules to your project and team, you should discuss it with them first. It is easy to think that some patterns are always better and want to enforce them, where in reality some edge cases exist where they aren't wanted. Also, people don't usually like it when seemingly arbitrary rules are imposed on them, especially if it relates to code style, so be sure to talk with them and explain the rationale.
The preferred method, if you have Node.js and npm installed, is to use node-elm-lint, which has instructions on how to install it. This will allow you to lint your whole project.
Also, you can try the online version here, where you can copy-paste your source code and see the linting errors.
Configuration is done via an Elm file. The benefit of having the configuration written in Elm, is having nicer error messages when there is a misconfiguration, potential auto-completion, and more explicit rule locations (no need for some magic to find the rules defined by a package for instance). Since the rules are written in Elm, they are publishable on the Elm package registry, and writing them should be more accessible than if it was written in a different language.
module LintConfig exposing (config)
import Lint.Rule exposing (Rule)
import Third.Party.Rule
import My.Own.Custom.rule
import Another.Rule
config : List Rule
config =
[ Third.Party.Rule.rule
, My.Own.Custom.rule
, Another.Rule.rule { ruleOptions = [] }
]You can get started with an empty configuration with the command line tool by running
elm-lint init, which you can then add rules to. Before you do, I suggest
reading the rest of this document, but especially the section on
when to enable a rule in your configuration.
elm-lint does not come with any built-in rules. You can read why here. You can get rules from packages in the Elm package registry by going to your lint directory and running elm install in your terminal.
cd lint
elm install authorName/dependencyNameYou can also write your own rules, as shown in the next section.
You can write your own rule using this package's API and
elm-syntax.
Check out the Lint.Rule module for more instructions.
Here's an example of a rule that prevents a typo in a string that was made too often at your company.
module NoStringWithMisspelledCompanyName exposing (rule)
import Elm.Syntax.Expression exposing (Expression(..))
import Elm.Syntax.Node as Node exposing (Node)
import Lint.Rule as Rule exposing (Error, Rule)
-- Create a new rule
rule : Rule
rule =
-- Define the rule with the same name as the module it is defined in
Rule.newSchema "NoStringWithMisspelledCompanyName"
-- Make it look at expressions
|> Rule.withSimpleExpressionVisitor expressionVisitor
|> Rule.fromSchema
-- This function will visit all the expressions (like `1`, `"string"`, `foo bar`, `a + b`, ...)
-- and report problems that it finds
expressionVisitor : Node Expression -> List Error
expressionVisitor node =
case Node.value node of
-- It will look at string literals (like "a", """a""")
Literal str ->
if String.contains "frits.com" str then
-- Return a single error, describing the problem
[ Rule.error
{ message = "Replace `frits.com` by `fruits.com`"
, details = [ "This typo has been made and noticed by users too many times. Our company is `fruits.com`, not `frits.com`." ]
}
-- This is the location of the problem in the source code
(Node.range node)
]
else
[]
_ ->
[]Then add the rule in your configuration:
module LintConfig exposing (config)
import Lint.Rule exposing (Rule)
import NoStringWithMisspelledCompanyName
config : List Rule
config =
[ NoStringWithMisspelledCompanyName.rule
-- other rules...
]The bar to write or enable a rule in your configuration should be pretty high. I recommend having the default answer to the question "Should I write/enable this rule?" be "no". I think that the ratio that compares the rule's value to the nuisances it will cause should be very high, and it is often hard to foresee all the nuisances.
Linting rules are useful when something must never appear in the code. It gets
much less useful when something should not appear only 99% of the time, as there
is no good solution for handling exceptions (elm-lint doesn't offer an option
for disabling a rule locally, see why here).
If you really need to make exceptions, they should be written in the rule itself
or defined in the rule's parameters.
First of all, if you have never encountered a problem with some pattern before, then you probably don't need to forbid it. There are chances the problem will never occur, and writing the rule is a waste of time. Or maybe when using it, you will find that the pattern is actually not so bad at all and that there are situations where using it is actually the best option.
For rules that enforce a certain coding style, or even suggest simplifications to your code, I would ask you to raise the bar even higher, as I think it is rarely applicable or better 100% of the time. A few examples:
|> over <|, and I think using the latter to pipe
functions over several lines is harder to read. Even if using |> was indeed
better for most situations and even if my teammates agree, this would prevent
me from writing tests the suggested way
for instance.findFiles [] folder is
harder to understand than findFiles { exceptions = [] } folder.It is very important to communicate with your teammates about the rule and that they agree. Fighting to avoid a rule is very frustrating if the user who gets the error actually thinks the code would be better with the forbidden pattern. It is possible they will try to circumvent the rule in a way that was not the suggested way, probably making the code worse than before.
Some rules might suggest using more advanced techniques to avoid some pitfalls, and this might make it harder for newcomers or beginners to get something done. When enabling this kind of rule, make sure that it is helpful enough to unblock users, otherwise this can frustrate and/or block them.
When wondering whether to write or enable a rule, I suggest using this checklist:
There is none at the moment, for several reasons:
ESLint does it.
But since elm-format would move the
comments around, this would require you to try and figure out how/where to
place the comment, or the rule would need to be disabled for a bigger section
of the code than wanted. Neither option provides a good experience.elm-lint too. Obviously,
not being able to ignore a rule means that the bar to write or enable a rule
should be even higher.I suggest (re-)reading when to write or enable a rule in a configuration if you really need to ignore an error.