WartRemover: a Scala linter that ships as a compiler plugin
Flexible Scala code linting tool
At a glance
- What is it?
- WartRemover is a Scala code linting tool whose rules are enforced by the compiler rather than by a separate scan, and the build is a multi module sbt project with a core library, an sbt plugin, an inspector, a test macros module and a benchmark. The one thing the documentation spends real effort on is how to file a useful bug, and the answer is to paste the compiler's own expanded tree rather than the source you wrote.
- Who is it for?
- WartRemover fits a Scala team that wants lint rules enforced at compile time, with the failures attached to the code that caused them rather than reported in a separate report. It is the wrong tool if your build does not run the Scala compiler as part of every compilation, since a compiler plugin only fires when the compiler runs, and it is a poor fit for a codebase that is not on the Scala versions the plugin supports.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the tool is, and the one workflow detail that matters
WartRemover is a flexible Scala code linting tool, and the topic tags name the mechanism: compiler-plugin, linter, macros and scala.
The compiler plugin part is the design decision that distinguishes it from a linter that runs separately. The rules are enforced during compilation, so a violation is a compile error rather than a line in a report that someone has to remember to run. And the failure is attached to the code that caused it, which is the property that decides whether a rule survives contact with a large codebase.
The macros tag points at the other half of the implementation, which is how a rule can be expressed in a way the compiler can enforce. Some lint rules are just a check on the syntax tree, and those are the easy case. The rules that are worth having are the ones about types, about values that flow in a particular way, or about patterns that look right but are not, and expressing those as compiler macros is what makes the approach work rather than being a toy.
The project publishes the documentation on its own site, wartremover.org, and the API documentation for the rules is generated as scaladoc, with the badge pointing at the org.wartremover.wartremover_3 artifact and its warts page. The Maven Central badge is for that same artifact.
The primary language is Scala, the licence is Apache 2.0 with the text in the LICENSE file, and the homepage is wartremover.org. The repository is not archived, and the last push was on 2026-10-09.
The release history is where the version story gets interesting. The three listed tags are v2.4.5 from 2020-02-25, v2.4.4 from 2020-02-19 and v2.4.3 from 2019-09-19. So the GitHub releases stop in February 2020, while the artifacts that the scaladoc and Maven Central badges point at are the version 3 line, and the last push is more than six years after the newest tag. The project is still being worked on, but the release tags are not where that work shows up, so the version you depend on is whatever is on Maven Central rather than the newest tag in the repository.
The module layout, and what a multi module sbt project implies
The repository layout is a multi module sbt build, and reading the directories tells you what the project is made of.
There is core/, which is the library where the rules themselves live, and a wartremover/ directory which is the published artifact directory. There is sbt-plugin/, so the tool has an sbt plugin form, which is how the rules get switched on for a build rather than each developer adding them by hand. There is inspector/ and inspector-common/, a separate pair of modules. There is test-macros/, which is where the macros used to test the rule macros live, and it is a separate module because macro code has to be compiled and tested differently from ordinary code.
There is project/ for the sbt build definition, build.sbt at the root, and version.sbt, which is where the version is set. And there is a benchmark/ directory, which tells you the project takes the performance of the linting itself seriously, which matters because a compiler plugin runs on every compilation of every developer.
Two configuration files are worth noting because they tell you how the project is maintained. There is.scalafmt.conf, so the Scala source is formatted by scalafmt, and.scala-steward.conf, which configures Scala Steward. Configuring it means the build's own dependencies are updated automatically rather than by hand.
The docs/ directory is present, and the README points to the site rather than duplicating the rule reference, which is the right division. A linter with a large rule catalogue does not belong in a README.
The result is a build that is a normal sbt project, and a tool that is a normal library dependency. There is no separate lint step to wire into a pipeline, because the linting happens while the code is compiled. That is the whole appeal, and it is also the whole constraint: the rules only apply to compilations that go through the plugin.
The two artefacts, and the version number that does not match the tags
There is a detail in the project metadata that would confuse anyone evaluating it, and it is worth spelling out because it affects which artefact you depend on.
The scaladoc badge points at the artifact org.wartremover.wartremover_3 and the warts page under it. The Maven Central badge is for org.wartremover.wartremover_3 as well. So the published, documented, current line of the project is the version 3 series, under the Scala 3 cross build convention where the artifact suffix carries the binary version.
The GitHub release tags, on the other hand, are v2.4.5 from 2020-02-25, v2.4.4 from 2020-02-19 and v2.4.3 from 2019-09-19. The tag naming carries an explicit v prefix, and the version numbers are in the 2.x series.
So there are two naming schemes in play, and they belong to different eras of the project. The release tags are the Scala 2 line and they stopped in early 2020. The artefacts on Maven Central and the rule documentation are the Scala 3 line. The repository itself carries on, with the last push in October 2026, and the work is evidently happening in the tree rather than in a new tag.
The practical consequence is that you cannot use the tag history as a version reference. No version 3 release tag appears among the recent releases, so the tag history is not a reliable version reference.
It also tells you something about the shape of the project. A linter built as compiler macros is tied to the compiler's own macro API, and that API changed substantially between Scala 2 and Scala 3. Moving a rule from one to the other is not a mechanical port, which is why the two lines have separate artefact names and why the tag history stops where it does. The rules that survive that transition are the ones whose logic could be expressed in terms that both compilers support.
The.scala-steward.conf in the repository is relevant here, and for a project that has to track two compiler lines, that automation is what keeps the older line building as the newer one moves. It is a small file with a disproportionate amount of work behind it.
How to file a bug that can actually be acted on
The Reporting Issues section is the most practically useful part of the documentation, and it is unusual in how specific it is.
It is very useful to get the tree expanded by the Scala compiler, rather than the original source. And the way to do that is to add the -Vprint:typer flag to the Scala compiler, which shows code like the following:
// println("Hello world")
package $line4 {
object $read extends scala.AnyRef {
def <init>(): $line4.$read.type = {
$read.super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
private[this] val res1: Unit = scala.this.Predef.println("Hello world");
<stable> <accessor> def res1: Unit = $iw.this.res1
}
}
}
}Adding the generated code to an issue is very useful for debugging.
The reason this matters is specific to how WartRemover works. A rule that fires is a rule that has analysed the typed tree, because the rules are compiler macros and they see types, not text. So when a rule produces a confusing error, the source you wrote is often not what the rule saw. The expanded tree with the $line and $read names is what the compiler actually passed to the macro, and it is the only representation that lets a maintainer reproduce the analysis.
The -Vprint:typer flag is a compiler debug flag, and asking a user to add it and paste the result is a fairly high bar for a bug report, and a fairly low one for the person who has to act on it.
This is a small but characteristic thing about the project. A linter that runs on the typed tree produces errors that are hard to reason about from the source, and the documentation deals with that directly instead of leaving each reporter to work it out. The expanded tree in the example is a println call that has been desugared into an accessor and a stable value, and the fact that the rule saw that rather than the original line is the whole explanation of why the output looks unfamiliar.
The other half of the reporting guidance is the scope note in the reporting section, that it is very useful to get the tree expanded rather than the original source, which tells you the original source is not what the maintainer needs. The reference to the online documentation comes first, so a reporter is expected to have checked the rule's own documentation before filing.
What linting through the compiler costs you, and where the limits are
There are two things to weigh before adopting a compiler plugin based linter, and both are consequences of the same design rather than separate limitations.
The first is that the rules only run when the compiler runs. That is the mechanism, and it has consequences in both directions. A rule violation cannot slip into a commit, which is the point. But a codebase that is built by something other than the Scala compiler, a code generator that emits Scala, a build that uses a different linter, or an editor that type-checks without the plugin, will not have the rules applied at all. A linter you can forget to run is a linter you will forget to run.
The second is that the rules operate on the typed tree, which is what gives them their power and also what makes their errors less obvious. A rule that can check whether a value is used safely has to see the types, and a rule that sees types reports on the desugared form of the code rather than on the line you wrote. The Reporting Issues section is the documentation acknowledging that directly, and it is a reminder that the error messages from a macro based linter are not always going to read like a compiler error about your code.
The third consideration is the maintenance shape of the project, which the metadata tells you plainly. The release tags stop in February 2020, the artefacts the documentation points at are on the version 3 line, and the last push is from October 2026. So the code is being maintained, the published artefacts are ahead of the release tags, and the version of WartRemover you depend on is whatever is on Maven Central.
That combination of facts is not a red flag on its own. A linter is a dependency like any other, and the Apache 2.0 licence lets you vendor it, patch it, or write your own rule, provided you keep the copyright and licence notices. But it does mean that if you are evaluating the project on the basis of the release history, you are reading a misleading picture, and if you are depending on a specific rule, you should check that the rule exists in the version you are pulling rather than in the tag history.
The project's own framing is that it is flexible, which is the word to hold onto when you extend it. A rule is a macro plus a configuration entry, so a team that needs a check the rules catalogue does not have can write one, and the test-macros/ module in the repository is the place where that kind of work is exercised.
Editorial conclusion
WartRemover fits a Scala team that wants lint rules enforced at compile time, with the failures attached to the code that caused them rather than reported in a separate report. It is the wrong tool if your build does not run the Scala compiler as part of every compilation, since a compiler plugin only fires when the compiler runs, and it is a poor fit for a codebase that is not on the Scala versions the plugin supports. Before adopting, read the rules documentation on the project site rather than assuming the rule names are self explanatory, and check the version you depend on, because the published release tags stop in 2020 while the artifacts the documentation points at are version 3.
Frequently asked questions
What is WartRemover and how does it check Scala code?
It is a Scala code linting tool that runs as a compiler plugin, with the topic tags naming compiler-plugin, linter, macros and scala. Because the rules are enforced during compilation rather than by a separate scan, a violation is a compile error attached to the code that caused it.
How should I report a WartRemover bug?
By adding the -Vprint:typer flag to the Scala compiler and attaching the expanded tree it prints to the issue, rather than the original source. The documentation says it is very useful to get the tree expanded by the Scala compiler rather than the original source, because the rules analyse the typed tree, and adding the generated code to an issue is very useful for debugging.
What modules does the WartRemover repository contain?
A multi module sbt project with core/ for the rules, a wartremover/ directory for the published artifact, sbt-plugin/ for the build plugin, inspector/ and inspector-common/, test-macros/ for the macros used to test the rule macros, a benchmark/ directory, and project/ with build.sbt and version.sbt at the root. There is also .scalafmt.conf and a .scala-steward.conf for automatic dependency updates.
What licence is WartRemover released under?
The Apache Software License, Version 2.0, with the text in the LICENSE file in the repository. The published artifacts are on Maven Central under the org.wartremover group, and the rule reference is published as scaladoc.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wartremover-wartremover)Was this useful?