Skip to content

Commit d9499ba

Browse files
Chimaobi Emeka-IheonuChimaobi Emeka-Iheonu
authored andcommitted
Support IntelliJ IDEA external annotations (annotations.xml); fixes #6258
1 parent 8287368 commit d9499ba

10 files changed

Lines changed: 841 additions & 5 deletions

File tree

docs/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
### User-visible changes
99

10+
Added support for IntelliJ IDEA external annotations (`annotations.xml`) via the
11+
`-AexternalAnnotations` command-line option.
12+
1013
The `-AsuggestPureMethods` command-line option and the `purity.effectively.pure`
1114
warning no longer require `-AcheckPurityAnnotations` to also be supplied.
1215

docs/manual/annotating-libraries.tex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,28 @@
10811081
\end{Verbatim}
10821082

10831083

1084+
\sectionAndLabel{External annotations (IntelliJ format)}{intellij-external-annotations}
1085+
1086+
The Checker Framework can read external annotations stored in IntelliJ IDEA's \code{annotations.xml} format.
1087+
This allows users to create external annotations using IntelliJ IDEA's user interface and supply them directly
1088+
to the Checker Framework.
1089+
1090+
\subsectionAndLabel{Using external annotations}{external-annotations-using}
1091+
1092+
To supply external annotations to a checker, pass the \code{-AexternalAnnotations} command-line argument.
1093+
It takes a colon-separated (on Unix) or semicolon-separated (on Windows) list of directories, JAR files, or ZIP files
1094+
containing \code{annotations.xml} files arranged in directory structures matching their package names.
1095+
1096+
For example:
1097+
\begin{myxsmall}
1098+
\begin{Verbatim}
1099+
javac -processor org.checkerframework.checker.nullness.NullnessChecker \
1100+
-AexternalAnnotations=path/to/annotations-dir:path/to/annotations.jar \
1101+
MyFile.java
1102+
\end{Verbatim}
1103+
\end{myxsmall}
1104+
1105+
10841106
\sectionAndLabel{Troubleshooting/debugging annotated libraries}{libraries-troubleshooting}
10851107

10861108
Sometimes, it may seem that a checker is treating a library as unannotated

framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@
271271
// Additional ajava files to use
272272
// org.checkerframework.framework.type.AnnotatedTypeFactory.parserAjavaFiles()
273273
"ajava",
274+
// Additional external annotations (IntelliJ annotations.xml format)
275+
// org.checkerframework.framework.stub.AnnotationFileElementTypes.parseExternalAnnotations()
276+
"externalAnnotations",
274277
// Whether to print warnings about types/members in a stub file
275278
// that were not found on the class path
276279
// org.checkerframework.framework.stub.AnnotationFileParser.warnIfNotFound

framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileElementTypes.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ public void parseStubFiles() {
202202
AnnotationFileType.COMMAND_LINE_STUB);
203203
}
204204

205+
// 6. External annotations provided via -AexternalAnnotations command-line option
206+
String externalAnnotationsOption = checker.getOption("externalAnnotations");
207+
if (externalAnnotationsOption != null) {
208+
parseExternalAnnotations(
209+
SystemUtil.pathSeparatorSplitter.splitToList(externalAnnotationsOption));
210+
}
211+
205212
parsing = false;
206213

207214
if (stubDebug) {
@@ -288,6 +295,50 @@ public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) {
288295
}
289296
}
290297

298+
/**
299+
* Parses the external annotations files (IntelliJ annotations.xml format) at the given paths.
300+
*
301+
* @param externalAnnotationPaths list of files, directories, or jars/zips to parse
302+
*/
303+
public void parseExternalAnnotations(List<String> externalAnnotationPaths) {
304+
if (externalAnnotationPaths.isEmpty()) {
305+
return;
306+
}
307+
SourceChecker checker = factory.getChecker();
308+
ProcessingEnvironment processingEnv = factory.getProcessingEnv();
309+
if (stubDebug) {
310+
AnnotationFileParser.stubDebugStatic(
311+
processingEnv, "AFET.parseExternalAnnotations(%s)", externalAnnotationPaths);
312+
}
313+
for (String path : externalAnnotationPaths) {
314+
String base = System.getProperty("test.src");
315+
String fullPath = (base == null) ? path : base + "/" + path;
316+
317+
List<AnnotationFileResource> allFiles =
318+
AnnotationFileUtil.allAnnotationFiles(fullPath, AnnotationFileType.EXTERNAL_ANNOTATIONS);
319+
if (allFiles != null) {
320+
for (AnnotationFileResource resource : allFiles) {
321+
try (InputStream annotationFileStream =
322+
new BufferedInputStream(resource.getInputStream())) {
323+
IntelliJAnnotationParser.parseAnnotationsXml(
324+
resource.getDescription(),
325+
annotationFileStream,
326+
factory,
327+
processingEnv,
328+
annotationFileAnnos);
329+
} catch (IOException e) {
330+
checker.message(
331+
Diagnostic.Kind.NOTE,
332+
"Could not read external annotations resource: " + resource.getDescription());
333+
}
334+
}
335+
} else {
336+
checker.message(
337+
Diagnostic.Kind.WARNING, "External annotations location not found: " + path);
338+
}
339+
}
340+
}
341+
291342
/**
292343
* Parses the files in {@code annotationFiles} of the given file type. This includes files listed
293344
* directly in {@code annotationFiles} and for each listed directory, also includes all files

framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileUtil.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ public enum AnnotationFileType {
6060
/** Ajava file being parsed as if it is a stub file. */
6161
AJAVA_AS_STUB,
6262
/** Ajava file provided on command line. */
63-
AJAVA;
63+
AJAVA,
64+
/** External annotations (IntelliJ annotations.xml format). */
65+
EXTERNAL_ANNOTATIONS;
6466

6567
/**
6668
* Returns true if this represents a stub file.
@@ -70,7 +72,7 @@ public enum AnnotationFileType {
7072
public boolean isStub() {
7173
return switch (this) {
7274
case JDK_STUB, BUILTIN_STUB, COMMAND_LINE_STUB, AJAVA_AS_STUB -> true;
73-
case AJAVA -> false;
75+
case AJAVA, EXTERNAL_ANNOTATIONS -> false;
7476
default -> throw new BugInCF("unhandled case " + this);
7577
};
7678
}
@@ -83,7 +85,7 @@ public boolean isStub() {
8385
public boolean isBuiltIn() {
8486
return switch (this) {
8587
case JDK_STUB, BUILTIN_STUB -> true;
86-
case COMMAND_LINE_STUB, AJAVA_AS_STUB, AJAVA -> false;
88+
case COMMAND_LINE_STUB, AJAVA_AS_STUB, AJAVA, EXTERNAL_ANNOTATIONS -> false;
8789
default -> throw new BugInCF("unhandled case " + this);
8890
};
8991
}
@@ -96,7 +98,7 @@ public boolean isBuiltIn() {
9698
public boolean isCommandLine() {
9799
return switch (this) {
98100
case JDK_STUB, BUILTIN_STUB -> false;
99-
case COMMAND_LINE_STUB, AJAVA_AS_STUB, AJAVA -> true;
101+
case COMMAND_LINE_STUB, AJAVA_AS_STUB, AJAVA, EXTERNAL_ANNOTATIONS -> true;
100102
default -> throw new BugInCF("unhandled case " + this);
101103
};
102104
}
@@ -400,11 +402,14 @@ private static boolean isAnnotationFile(File f, AnnotationFileType fileType) {
400402
* otherwise
401403
*/
402404
private static boolean isAnnotationFile(String path, AnnotationFileType fileType) {
405+
if (fileType == AnnotationFileType.EXTERNAL_ANNOTATIONS) {
406+
return path.endsWith("annotations.xml") || path.endsWith(".xml");
407+
}
403408
return path.endsWith(fileType.isStub() ? ".astub" : ".ajava");
404409
}
405410

406411
private static boolean isJar(File f) {
407-
return f.isFile() && f.getName().endsWith(".jar");
412+
return f.isFile() && (f.getName().endsWith(".jar") || f.getName().endsWith(".zip"));
408413
}
409414

410415
/**

0 commit comments

Comments
 (0)