Analyzes Elm projects, to help find mistakes before your users find them.
Go to file
2020-04-01 20:13:45 +02:00
demo Rename withModule to addModule and withParsedModule to addParsedModule 2020-01-28 23:06:45 +01:00
documentation Update elm-lint-to-elm-review.md 2020-04-01 17:07:00 +02:00
phantom-type-tests Move ProjectRuleSchema's phantom type to be the first type variable 2020-04-01 20:13:45 +02:00
review Add NoTodoComment to review configuration 2020-03-18 17:50:32 +01:00
src Add phantom type variable to Error 2020-04-01 20:13:45 +02:00
tests Fix wrong error message. Thanks Anton Komissarov! 2020-03-23 20:42:29 +01:00
.gitignore Ignore .idea files 2020-03-16 21:04:02 +01:00
.travis.yml Run tests in Travis 2019-07-31 21:01:48 +02:00
CHANGELOG.md Rename project to elm-review 2019-09-28 17:18:51 +02:00
elm.json Move Project.Dependency to its own module 2020-03-07 18:38:39 +01:00
LICENSE Add license file 2018-11-05 19:07:41 +01:00
package-lock.json Update elm and elm-test version 2020-03-13 15:42:19 +01:00
package.json Make phantom type tests work when run from any directory 2020-04-01 20:13:45 +02:00
README.md Slim down the README (#5) 2020-04-01 20:13:34 +02:00

elm-review

elm-review analyzes Elm code, to help find mistakes before your users find them.

elm-review reporter output

What does elm-review do?

elm-review analyzes your source code, trying to recognize code that is known to cause problems. All the rules describing problematic code are written in Elm, and elm-review does not come with any built-in rules; instead users are encouraged to write rules themselves and publish them as Elm packages, for everyone to benefit. Search the package registry to find what's out there!

Encouraging users to write rules also makes it easy to add custom rules that only apply to your project. Such as rules that:

  • enforce that e.g. image paths only live in an Images module, which other modules can reference.
  • make everyone use a common Button component, instead of creating their own.
  • help users of a library you made, to avoid making mistakes that your API could not prevent them from doing.

Beware how and why you introduce rules in your project though. Often a good API, that guides users to correct solutions, is the best way to go, so instead of writing a rule, maybe there is an API that can be improved? But if a rule seems like the best solution, remember to discuss it with your team. It's easy to mix up patterns that are objectively bad, with patterns that you personally find problematic, and forbidding patterns that other people find useful can be very disruptive.

Try it

The easiest way to run elm-review, if you have Node.js and npm installed, is to use the elm-review CLI tool.

# Save it to your package.json, if you use npm in your project.
# This is the recommended way.
npm install elm-review --save-dev

# Install globally. This is not recommended.
npm install -g elm-review

You can also try the online version here, where you can copy-paste your source code and see the review errors.

Configuration

Rules are configured in the ReviewConfig.elm file:

module ReviewConfig exposing (config)

import Review.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 a fresh configuration by running the elm-review init command with the command line tool installed. This will add a review folder to your project, which is a self-contained Elm project where you can write, import, and configure review rules. As elm-review does not come with any built-in rules, you can find existing rules using elm-search and searching for Review.Rule.Rule, and install them with the elm install command, just like any other Elm project dependency.

cd review/ # Go inside your review configuration directory
elm install authorName/packageName

Before you start adding rules though, I suggest reading the rest of this document, especially the section on when to enable a rule.

Write your own rule

You can write your own rule using this package's API and elm-syntax. Check out the Review.Rule documentation 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 Review.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.newModuleRuleSchema "NoStringWithMisspelledCompanyName" ()
        -- Make it look at expressions
        |> Rule.withSimpleExpressionVisitor expressionVisitor
        |> Rule.fromModuleRuleSchema

-- 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 ReviewConfig exposing (config)

import Review.Rule exposing (Rule)
import NoStringWithMisspelledCompanyName


config : List Rule
config =
    [ NoStringWithMisspelledCompanyName.rule
    -- other rules...
    ]

When to write or enable a rule

The bar to write or enable a rule should be pretty high. A new rule can often turn out to be a nuisance to someone, sometimes in ways you didn't predict, so making sure the rule solves a real problem, and that your team is on board with it, is important. If a developer disagrees with a rule, they may try to circumvent it, resulting in code that is even more error prone than the pattern that was originally forbidden. So the value provided by the rule should be much greater than the trouble it causes, and if you find that a rule doesn't live up to this, consider disabling it.

Review rules are most useful when some pattern must never appear in the code. It gets less useful when a pattern is allowed to appear in certain cases, as there is no good solution for handling exceptions to rules. If you really need to make exceptions, they must be written in the rule itself, or the rule should be configurable.

For rules that enforce a certain coding style, or suggest simplifications to your code, I would ask you to raise the bar for inclusion even higher. A few examples:

  • I much prefer using |> 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.
  • If a record contains only one field, then I could suggest not using a record and use the field directly, which would make things simpler. But using a record can have the advantage of being more explicit: findFiles [] folder is harder to understand than findFiles { exceptions = [] } folder.

Some rules might suggest using advanced techniques to avoid pitfalls, which can make it harder for newcomers to get something done. When enabling this kind of rule, make sure that the message it gives is helpful enough to unblock users.

When wondering whether to enable a rule, I suggest using this checklist:

  • I have had problems with the pattern I want to forbid.
  • I could not find a way to solve the problem by changing the API of the problematic code or introducing a new API.
  • If the rule exists, I have read its documentation and the section about when not to enable the rule, and it doesn't apply to my situation.
  • I have thought very hard about what the corner cases could be and what kind of patterns this would forbid that are actually okay, and they are acceptable.
  • I think the rule explains well enough how to solve the issue, to make sure beginners are not blocked by it.
  • I have communicated with my teammates and they all agree to enforce the rule.
  • I am ready to disable the rule if it turns out to be more disturbing than helpful.

Is there a way to ignore an error or disable a rule only in some locations?

You can prevent errors from being reported by either changing the implementation of your rules or by configuring exceptions for directories or for files.

It is however not possible to ignore errors on a case-by-case basis, for several reasons:

  • The most practical way to locally disable a rule is likely through comments, like how ESLint does it. But since elm-format moves comments around, this is not practical.
  • If there are some rules that you really want to enforce because you want to create additional guarantees in your codebase, and it is possible to ignore it, then you will need a second system to ensure those rules are never ignored.
  • When people encounter a review error, some will try to disable it by default, maybe because they disagree with the rule, are annoyed by it, or because they think they will fix the issue later or not at all. So make sure the rule provides real and obvious value!
  • If you are looking to make exceptions to a rule, really consider if the rule should just be disabled.