A customizable wrapper around javac that aggregates and outputs compiler diagnostics in a desired format using a pluggable architecture.
The javac diagnostics wrapper is part of the larger https://eisopux.github.io/ project.
./gradlew assemblewill generate build/libs/javac-diagnostics-wrapper-all.jar.
Simply use instead of your usual javac command.
Instead of
javac [flags] File1.java File2.java(where [flags] is a placeholder for 0 or more actual javac flags you're using)
use
java \
-cp /path/to/javac-diagnostics-wrapper-all.jar \
io.github.eisopux.diagnostics.builtin.JsonDiagnostics \
[flags] File1.java File2.java(where [flags] is a placeholder for 0 or more actual javac flags you're using)
There are currently two builtin outputs using a Diagnostics Collector:
io.github.eisopux.diagnostics.builtin.LspDiagnosticsproduces output in the LSP JSON format.io.github.eisopux.diagnostics.builtin.JsonDiagnosticsproduces output in a JSON format directly corresponding to the javac diagnostics.
Normal compilation of a file with errors, using the javac format:
java \
-cp /path/to/javac-diagnostics-wrapper-all.jar \
io.github.eisopux.diagnostics.builtin.JsonDiagnostics \
File1.javaresults in
{
"diagnostics": [
{
"source": "file:///.../File1.java",
"kind": "ERROR",
"position": 29,
"startPosition": 29,
"endPosition": 30,
"lineNumber": 2,
"columnNumber": 16,
"code": "compiler.err.prob.found.req",
"message": "incompatible types: int cannot be converted to java.lang.String"
},
{
"source": "file:///.../File1.java",
"kind": "ERROR",
"position": 64,
"startPosition": 64,
"endPosition": 69,
"lineNumber": 4,
"columnNumber": 16,
"code": "compiler.err.prob.found.req",
"message": "incompatible types: unexpected return value"
}
]
}
Compilation of a file using the Checker Framework, using the LSP format:
java \
-cp /path/to/javac-diagnostics-wrapper-all.jar \
io.github.eisopux.diagnostics.builtin.LspDiagnostics \
-classpath /path/to/checker-framework/checker/dist/checker.jar \
-processor org.checkerframework.checker.nullness.NullnessChecker \
-AshowPrefixInWarningMessages
File2.javaresults in:
[
{
"uri": "file://.../File2.java",
"diagnostics": [
{
"range": {
"start": {
"line": 12,
"character": 14
},
"end": {
"line": 12,
"character": 16
}
},
"severity": 1,
"code": "compiler.err.proc.messager",
"source": "javac",
"message": "[argument.type.incompatible] incompatible types in argument.\n found : @Initialized @Nullable InputStream\n required: @Initialized @NonNull InputStream"
}
]
}
]
Note that the -AshowPrefixInWarningMessages is an optional Checker Framework flag
and will attach correct processor information to formats that support this information.
To format the source code, run ./gradlew spotlessApply.
The javac diagnostics wrapper features a modular, pluggable design that decouples data collection from output formatting. It utilizes a collector-reporter interface that allows developers to easily implement custom diagnostic gathering and presentation formats.
Components that hook into the javac compilation process to gather desired information.
Each collector implements the Collector interface and contributes its collected data as
a list of key/value pairs to a centralized CompilationReportData instance.
Components that process the aggregated CompilationReportData and generate output in various formats
(e.g., console text, JSON, or LSP diagnostics). Each reporter implements the Reporter interface and is responsible for
formatting the data according to its output standard.
Create a new class that implements io.github.eisopux.diagnostics.core.Collector. Override:
onBeforeCompile(CompilationTaskBuilder builder)if you need to attach listeners or initialize data structures.onAfterCompile(CompilationReportData reportData)to finalize your data and populate a report section as a list of key/value pairs.
Create a new class that implements io.github.eisopux.diagnostics.core.Reporter and its
generateReport(CompilationReportData reportData) method. Format the data according
to your output requirements.
The javac-diagnositc-wrapper produces output by combining one or more Collectors with exactly one
Reporter. To create an easy-to-use output configuration, add a new
class to theio.github.eisopux.diagnostics.builtin package. This class will encapsulate
the desired collectors and reporter, providing a convenient entry
point for generating an output. An example implementation is shown below:
public class JsonDiagnostics {
public static void main(String[] args) {
CompilerRunner runner =
new CompilerRunner()
.addCollector(new DiagnosticCollector())
.setReporter(new JSONReporter());
runner.run(args);
}
}Where .addCollector should be called one or more times to combine
multiple collectors and .setReporter should be called exactly once to select
the desired output format.