Replies: 2 comments 2 replies
|
There is a way for Maven projects to support errorprone rules that run on javadoc comments without forking the compiler. The compiler can be configured with an annotation processor shim <plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<!-- Restores javac's analysis of /// documentation comments. javac converts them
through a provider it looks up against the thread context classloader, which
under Maven is the plugin's isolated realm, so it silently falls back to reading
every markdown comment as raw text and nothing written in one is checked. The
processor installs the provider itself; see the class for the mechanism.
Nothing here needs the rest of the artifact, so its dependencies are excluded
rather than added to every compilation's processor path. -->
<path>
<groupId>com.company</groupId>
<artifactId>company-build-tools</artifactId>
<version>${company-build-tools.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.lang.reflect.Method;
import java.util.ServiceLoader;
import java.util.Set;
import com.sun.source.util.DocTrees;
/**
* Installs the transformer that turns a markdown documentation comment into a documentation tree, which javac does
* not do for itself when it runs inside Maven.
*
* <p>javac has no markdown parser of its own. It converts a {@code ///} comment through a service provider in the
* {@code jdk.internal.md} module, which it looks up lazily with the single-argument {@code ServiceLoader.load} —
* that is, against the thread context classloader. Under Maven the thread context classloader is the plugin's
* isolated realm, the provider is not reachable from it, and javac installs an identity transformer instead without
* reporting anything.
*
* <p>Every markdown comment is then analysed as raw text. Block tags still parse, because javac reads those itself,
* so a checker looking for an unknown {@code @param} still works and the loss is invisible; but a markdown link is
* never built, so a checker looking for a broken reference inside one finds no references at all and passes the file.
* An IDE compiles with a classloader that can reach the provider, which is why the same file is rejected there and
* nowhere else.
*
* <p>Looking the provider up in the boot layer and installing it before the first comment is read restores the
* conversion. This runs as an annotation processor because that is the earliest point in a compilation at which a
* build tool hands out the compiler's own {@code DocTrees}.
*
* <p>It is reached reflectively for two reasons: {@code JavacTrees} sits in a package {@code jdk.compiler} does not
* export, and {@code --add-exports} cannot be combined with {@code --release}, so naming it directly would take this
* project off the release setting it builds with; and the type did not exist before JDK 23, while this project
* targets an earlier release. Failure to install is reported as a compiler warning rather than swallowed — a
* silent fallback is the defect this class exists to remove, and adding one here would hide the loss again.
*/
public final class MarkdownDocCommentAnalysis extends AbstractProcessor {
private static final String TRANSFORMER = "com.sun.tools.javac.api.JavacTrees$DocCommentTreeTransformer";
private static final String STANDARD = "standard";
@Override
public synchronized void init(ProcessingEnvironment environment) {
super.init(environment);
try {
install(DocTrees.instance(environment));
} catch (ReflectiveOperationException | RuntimeException e) {
environment.getMessager().printMessage(Diagnostic.Kind.WARNING,
"Markdown documentation comments will be analysed as raw text, so nothing written in one is"
+ " checked: " + e);
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment round) {
return false;
}
/**
* Installs the standard transformer on {@code trees}, unless the compiler already found it for itself.
*
* @param trees the compiler's own {@code DocTrees}, which is a {@code JavacTrees}
* @throws ReflectiveOperationException if the compiler is not the one this expects
*/
private static void install(DocTrees trees) throws ReflectiveOperationException {
Class<?> transformerType = Class.forName(TRANSFORMER);
Class<?> treesType = trees.getClass();
if (treesType.getMethod("getDocCommentTreeTransformer").invoke(trees) != null) {
return;
}
Method transformerName = transformerType.getMethod("name");
Method setter = treesType.getMethod("setDocCommentTreeTransformer", transformerType);
for (Object transformer : ServiceLoader.load(ModuleLayer.boot(), transformerType)) {
if (STANDARD.equals(transformerName.invoke(transformer))) {
setter.invoke(trees, transformer);
return;
}
}
throw new IllegalStateException("the boot layer offers no " + STANDARD + " implementation of " + TRANSFORMER);
}
} |
2 replies
|
Based on recent Error Prone and Google Java Format commit activity: CC @cpovirk, @kluever and @eamonnmcmanus. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Claude explains the issue:
javac doesn't understand markdown
///comments itself. It converts them to standardDocTrees via a service provider,jdk.internal.markdown.MarkdownTransformer, looked up inJavacTrees.initDocCommentTreeTransformer():Under Maven the TCCL is the plugin's isolated plexus-classworlds realm, which can't reach the boot-layer provider, so javac silently falls back to IdentityTransformer.
Your /// comments stay raw text,
[format]never becomes a LinkTree, and InvalidLink has nothing to match. IntelliJ's build runs javac under an ordinary classloader, loads the transformer, and sees the real link.Evidence, with byte-identical javac args (--release 25, ErrorProne 2.50.0,
-Xep:InvalidLink:ERROR) in all cases:The last row reproduces Maven exactly, which pins the mechanism.
The javadoc itself
IntelliJ is correct. In a markdown doc comment
[format]is a reference link to a program element, and a parameter isn't one — so it can never resolve. Code span is the right form, and it's already the house style in that very sentence ({}):-Xep:InvalidLink:ERRORis effectively dead in your Maven build, along with anything else that depends on markdown→DocTree conversion — every /// comment in the repo is analysed as raw text there. @param-style block tags still work (javac parses those itself), which is why InvalidParam fires and hides the gap.-Dmaven.compiler.fork=truewould restore it, but currently fails on invalid flag:-XepExcludedPathsbecause the multi-line-Xplugin:ErrorProneargument gets split when the plugin writes the forked argfile — that would need the ErrorProne args flattened to one line first if you want CI to catch these instead of the IDE.It would be good to mention this limitation before the text
If your maven-compiler-plugin uses an external executableat https://errorprone.info/docs/installationAll reactions