April 08, 2020
Today I am very excited to release
elm-review 2.0.0 and to share its new features!
tl;dr: Here is the list of the introduced features:
If you missed the initial announcement or if your memory is unclear, let me explain.
elm-review is a static code analysis tool for Elm, which looks at your Elm project, and reports patterns that you configured it to find using “rules” in a friendly Elm compiler-like way.
It is highly customizable because you can write your own rules. Since these are written in Elm, you don’t need to know a different language to do so, and you can even publish them as Elm packages!
I put a lot of effort into making a very nice API for you to use (here are some examples), even going through the lengths of discovering new Elm techniques (expect to see blog posts on the phantom builder pattern on this blog), and I am very happy with the result!
elm-review looks like a linter. Which isn’t necessarily wrong, since you can enable rules that help improve the quality of your code and enforce coding conventions for your team.
If you dive a bit further, you will find out that it can create new guarantees that the Elm compiler can not give you.
For instance you can write rules that
Regex.fromString calls have invalid regexes (for literal strings)
elm-review v1, the analysis broke down as soon as a problem spanned multiple modules though, but that’s now solved by:
elm-review version 1,
ESLint and a lot of similar tools for other languages, the analysis is scoped to a single file.
This means that when a rule has finished looking at module A, and starts looking at module B, it forgets everything about module A. This makes a lot of useful analysis untenable, like:
xyz come from? If a module is imported using
import A exposing (..), then we potentially lose the ability to tell. We have the same problem with types when encountering
import A exposing (B(..)).
In addition to the module rules that we just described,
elm-review now has project rules. These rules go through all the modules and can use information collected from a different module to infer or report things, solving the problems mentioned above.
In short, this feature makes rules much more accurate, and allow for almost any of them to be complete. No more “best effort” due to the limitations of looking at a single module.
This opens up a wide range of possibilities. A few example use-cases:
Html.lazy is used incorrectly
update function but not the
In version 1, reading the contents of
elm.json was possible, but it wasn’t possible to report errors for it.
docs.json file for the dependencies contain all the public information about your direct and indirect dependencies. Reading these can tell you which dependency a module comes from, along what types and functions they contain.
This gives you a lot of information about your direct and indirect dependencies, for instance what modules they contain and which functions and types are defined in there.
With this information, rules can be much more accurate. For instance, knowing what is added to the scope when encountering
import Xyz exposing (..) becomes possible.
Added to the new information you get from the previous points, you now have sufficient knowledge to replicate the compiler’s type inference logic!
exposing (..) or
exposing(..) (coming soon in
The README is an integral part of the project, especially for packages. For package authors and their users, it is important that everything in there is correct.
This version introduces a visitor for the README, which allows you to collect data and report errors on it. Those errors can be fixed automatically.
README.md point to the right version
README.md point to existing modules
With this version, you can also look at the contents of the comments or documentation of a function or module (To be honest, this was just me forgetting to add a visitor for it, it would have been an easy addition to v1).
Example use-cases (most of these are on my todo list for
@docs in the module documentation are always correct and up to date
Some tasks, like targeting a specific function, are harder than they should be. For instance, if you want to forbid
Foo.bar, you’ll need to handle multiple ways that the function can be called and imported, which is tedious and error-prone.
I started writing a helper named
elm-review-scope that deals with this problem, and makes some tasks as easy as they should be.
I am not making it part of
elm-review nor publishing it as a separate package though, because the API is still unstable.
Version 1 focused on usability and on validating that
elm-review was a good solution for these problems. Performance was secondary.
With version 2, performance was a focus, and the results are really good. Parsed files are now cached, so the initial run is still a bit slow, but subsequent runs are faster by several times.
There is also a watch mode where the changes feel instantaneous.
And I am sure we can do much better for future versions! The work done here should help pave the way to having editor support.
When you enabled a rule in version 1, you weren’t able to ignore any errors that it reported. You had to edit or fork the rule to ignore the cases you wanted to ignore.
The idea was to avoid ignoring errors locally through a comment of some sort, like what happens all over the place with
ESLint (which leads to all sorts of problems). Instead users should think on whether enabling a rule is a good idea in the first place. And I still stand by these choices!
But there are places where it’s reasonable to ignore review errors, namely for generated code and vendored code, and for introducing a rule gradually when there are too many errors. Sometimes, it also makes sense to have tests follow slightly different rules.
In these cases, you can configure your rules to not apply on a section on some directories or some files.
Version 1 had a
fix flag, where it would propose automatic fixes for some of the problems that it knew how to fix, and you could accept or refuse them.
For some, it was very annoying to go through all the fixes as this could be long and tedious when you had a lot of errors.
So this version comes with a
--fix-all where you get one big diff between the current source and the one where all fixes have been applied, and you can accept or refuse it.
I do not believe automatic fixes to always be perfect, and I know that there are some kinks in a few of the ones I wrote, so please be cautious when looking at the before/after diff and don’t commit the changes blindly.
The default structure version 1 created for you didn’t allow you to run tests for your rules due to conflicts with
The “review application” that you now get from running
elm-review init is structured in a way that will make tests work out of the box.
elm-review init now adds the dependencies needed to write rules by default too.
tests/ directory is now included by default. Since they are part of an Elm project, it makes sense to review them too.
Until now, the catalog of rules has been quite small, and I can understand that for many people this was a blocker for adoption.
Along with this release, I am publishing more rules than the 3 I had previously written. You can find them by searching for
jfmengels/review- in the packages website (and in general by looking for
/review- for other people’s packages), but among them are:
I am also working on a package to improve the quality of the documentation, which should help out package authors especially. And there are also some rules that I wrote, but am still unsure as to whether I want to maintain them personally, that you can copy over from
Elm Analyse is at the moment the de facto static code analysis tool for Elm, but it has a different philosophy from
Elm Analyse wants to improve the quality of the code by enabling rules that work for “everyone”.
elm-review on the other hand aims to create guarantees tailored to your team and project, while enabling ways to improve the quality of the code too in a shareable manner.
If you think you have a rule that could be of use to everyone, you can share it by publishing it in the Elm package registry.
Most checks (their naming for a rule) you can find in
Elm Analyse are available in the packages I published or GitHub repos I have written. The remaining ones are ones that are outdated or that I disagree with, but these are reasonably easy to write using this package’s API. (Exception for the checks for unused patterns and unused arguments which I plan to add). And anyone can publishing the missing ones if they care to.
From the tests I have run, I found
elm-review to be faster. I am guessing that that is mostly because rules are built in a way that avoids duplicate work.
Here are the things that
Elm Analyse has and
elm-review does not:
elm-review has a similar CLI watch mode.
As for the rest, I hope to have shown in this post and in the original announcement what this tool can do that
Elm Analyse can not.
If you already use
elm-review in your projects, you can follow this migration guide. Otherwise, follow these instructions!
# Using npm
npm install --save-dev elm-review
# Using Yarn
yarn add --dev elm-review
# Create a configuration
npx elm-review init
# Install dependencies that contain rules
elm install jfmengels/review-unused
elm install jfmengels/review-common
elm install jfmengels/review-debug
import Review.Rule exposing (Rule)
config : List Rule
, NoImportingEverything.rule 
, NoUnused.CustomTypeConstructors.rule 
Finally, run it using
That should get you started!
I recommend reading the documentation before you go too far in, which will give you advice on how to best set up
elm-review for your project and/or team.
I hope you will try
elm-review and enjoy it. I spent a lot of time polishing it to give you a great experience using it and writing rules, but there is room for a lot of improvement.
If you would like to help, I would love help to get this tool working in the different editors. You can also publish awesome rules (I’m here for advice or feedback!), or write about the tool in blog posts.
I would love to hear from you if you have constructive criticism, want to help out, want to share what you are using it for, or just want to share that you enjoy it (yes, that helps a lot).
There is an
#elm-review channel on the Elm Slack where you can do that or ask for help, and you can talk to me privately at
@jfmengels over there.