Linting Java Projects
This page will discuss common topics around enforcing the code quality of Java codebases using the Mill build tool
Linting with ErrorProne
Error Prone augments the Java compiler’s type
checker and detect common mistakes at compile time. Mill supports ErrorProne via
ErrorProneModule
: mix ErrorProneModule
into your JavaModule
and it will
automatically run with every compilation.
package build
import mill._, javalib._, errorprone._
object `package` extends RootModule with JavaModule with ErrorProneModule {
def errorProneOptions = Seq("-XepAllErrorsAsWarnings")
}
package example;
import java.util.HashSet;
import java.util.Set;
public class ShortSet {
public static void main(String[] args) {
Set<Short> s = new HashSet<>();
for (short i = 0; i < 100; i++) {
s.add(i);
s.remove(i - 1);
}
System.out.println(s.size());
}
}
When adding the ErrorProneModule
to your JavaModule
,
the error-prone
compiler plugin automatically detects various kind of programming errors.
> ./mill show errorProneOptions
[
"-XepAllErrorsAsWarnings"
]
> ./mill compile
[warn] .../src/example/ShortSet.java:11:15: [CollectionIncompatibleType] Argument 'i - 1' should not be passed to this method; its type int is not compatible with its collection's type argument Short
[warn] s.remove(i - 1);
[warn] ^ (see https://errorprone.info/bugpattern/CollectionIncompatibleType)
[warn] 1 warning
[warn] ^
build.mill.scala
: Enable ErrorProne
in a modulepackage build
import mill._, scalalib._
import $ivy.`com.lihaoyi::mill-contrib-errorprone:`
import mill.contrib.errorprone.ErrorProneModule
object foo extends JavaModule with ErrorProneModule {
}
Configuration
The following configuration options exist:
def errorProneVersion: T[String]
-
The
error-prone
version to use. Find the list of versions and changlog at https://github.com/google/error-prone/releases def errorProneOptions: T[Seq[String]]
-
Options directly given to the
error-prone
processor. Those are documented as "flags" at https://errorprone.info/docs/flags
Linting with Checkstyle
CheckstyleModule
Performs quality checks on Java source files using
Checkstyle and generates reports from these checks.
package build
import mill._, javalib._, checkstyle._
object `package` extends RootModule with CheckstyleModule {
def checkstyleVersion = "9.3"
}
To use this plugin in a Java/Scala module,
-
Extend
mill.contrib.checkstyle.CheckstyleModule
. -
Define a configuration file
checkstyle-config.xml
. -
Run the
checkstyle
command.
> ./mill checkstyle # run checkstyle to produce a report, defaults to warning without error
...src/InputWhitespaceCharacters.java:3:23: Line contains a tab character...
...src/InputWhitespaceCharacters.java:16:3: Line contains a tab character...
...src/InputFileName1.java:2:1: Top-level class MyAnnotation1 has to reside in its own source file...
...src/InputFileName1.java:13:1: Top-level class Enum1 has to reside in its own source file...
...src/InputFileName1.java:26:1: Top-level class TestRequireThisEnum has to reside in its own source file...
Audit done.
> sed -i.bak 's/warning/error/g' checkstyle-config.xml # make checkstyle error on violations
> ./mill checkstyle
error: ...src/InputWhitespaceCharacters.java:3:23: Line contains a tab character...
...src/InputWhitespaceCharacters.java:16:3: Line contains a tab character...
...src/InputFileName1.java:2:1: Top-level class MyAnnotation1 has to reside in its own source file...
...src/InputFileName1.java:13:1: Top-level class Enum1 has to reside in its own source file...
...src/InputFileName1.java:26:1: Top-level class TestRequireThisEnum has to reside in its own source file...
Audit done.
> sed -i.bak 's/\t/ /g' src/InputWhitespaceCharacters.java
> rm src/InputFileName1.java
> ./mill checkstyle # after fixing the violations, checkstyle no longer errors
Audit done.
Checkstyle Flags
// if an exception should be raised when violations are found
./mill checkstyle --check
// if Checkstyle output report should be written to System.out
./mill checkstyle --stdout
Checkstyle Sources (optional)
// incorrect paths will cause a command failure
./mill checkstyle a/b
// you can specify paths relative to millSourcePath
./mill checkstyle src/a/b
// process a single file
./mill checkstyle src/a/B.java
// process multiple sources
./mill checkstyle src/a/b src/c/d src/e/F.java
// process with flags
./mill checkstyle --check --stdout src/a/b src/c/d
// process all module sources
./mill checkstyle
Shared Checkstyle Configuration
To share checkstyle-config.xml
across modules, adapt the following example.
import mill._
import mill.contrib.checkstyle.CheckstyleModule
import mill.scalalib._
object foo extends Module {
object bar extends MyModule
object baz extends Module {
object fizz extends MyModule
object buzz extends MyModule
}
trait MyModule extends JavaModule with CheckstyleModule {
override def checkstyleConfig = Task {
api.PathRef(T.workspace / "checkstyle-config.xml")
}
}
}
Limitations
-
Version
6.3
or above is required forplain
andxml
formats. -
Setting
checkstyleOptions
might cause failures with legacy versions.
CheckstyleXsltModule
This plugin extends the mill.contrib.checkstyle.CheckstyleModule
with the ability to generate reports by applying XSL Transformations on a Checkstyle output report.
Auto detect XSL Transformations
XSLT files are detected automatically provided a prescribed directory structure is followed.
/**
* checkstyle-xslt
* ├─ html
* │ ├─ xslt0.xml
* │ └─ xslt1.xml
* └─ pdf
* ├─ xslt1.xml
* └─ xslt2.xml
*
* html/xslt0.xml -> xslt0.html
* html/xslt1.xml -> xslt1.html
* pdf/xslt1.xml -> xslt1.pdf
* pdf/xslt2.xml -> xslt2.pdf
*/
Specify XSL Transformations manually
For a custom setup, adapt the following example.
import mill._
import mill.api.PathRef
import mill.contrib.checkstyle.CheckstyleXsltModule
import mill.contrib.checkstyle.CheckstyleXsltReport
import mill.scalalib._
object foo extends JavaModule with CheckstyleXsltModule {
override def checkstyleXsltReports = Task {
Set(
CheckstyleXsltReport(
PathRef(millSourcePath / "checkstyle-no-frames.xml"),
PathRef(T.dest / "checkstyle-no-frames.html"),
)
)
}
}
AutoFormatting with Palantir Java Format
Mill supports auto-formatting Java code via the Palantir Java Format project.
package build
import mill._
import mill.javalib.palantirformat._
object `package` extends RootModule with PalantirFormatModule
public class A {
public static void main(String[] args) {
System.out.println("hello"); // indentation should be fixed
}
}
Palantir Java Format can be used on a per-module basis by inheriting from
PalantirFormatModule
and running the palanatirformat
command on that module
You can also use --check
if you wish to error if the code is not formatted,
which is useful in CI validation jobs to ensure code is formatted before merging.
> ./mill palantirformat --check # check should fail initially
...checking format in java sources ...
...src/A.java
error: ...palantirformat aborted due to format error(s) (or invalid plugin settings/palantirformat options)
> ./mill palantirformat # format all Java source files
...formatting java sources ...
> ./mill palantirformat --check # check should succeed now
...checking format in java sources ...
You can also use Palantir Java Format globally on all JavaModule`s in your build by running
`mill.javalib.palantirformat.PalantirFormatModule/
.
> ./mill mill.javalib.palantirformat.PalantirFormatModule/ # alternatively, use external module to check/format
...formatting java sources ...
If entering the long fully-qualified module name
mill.javalib.palantirformat.PalantirFormatModule/
is tedious, you can add
an External Module Alias
to give it a shorter name that’s easier to type
Code Coverage with Jacoco
Mill supports Java code coverage analysis via the mill-jacoco plugin. See the plugin repository documentation for more details:
Binary Compatibility Enforcement
If you want to lint against binary compatibility breakages, e.g. when developing an upstream library that downstream libraries may compile against, you can use the Lightbend Migration Manager (MiMa) tool via the mill-mima plugin. See the mill-mima documentation for more details: