RFR: 8331051: Add an `@since` checker test for `java.base` module
Jonathan Gibbons
jjg at openjdk.org
Mon Apr 29 09:16:30 UTC 2024
On Wed, 24 Apr 2024 14:10:29 GMT, Nizar Benalla <duke at openjdk.org> wrote:
> This checker checks the values of the `@since` tag found in the documentation comment for an element against the release in which the element first appeared.
>
> Real since value of an API element is computed as the oldest release in which the given API element was introduced. That is:
> - for modules, classes and interfaces, the release in which the element with the given qualified name was introduced
> - for constructors, the release in which the constructor with the given VM descriptor was introduced
> - for methods and fields, the release in which the given method or field with the given VM descriptor became a member of its enclosing class or interface, whether direct or inherited
>
> Effective since value of an API element is computed as follows:
> - if the given element has a `@since` tag in its javadoc, it is used
> - in all other cases, return the effective since value of the enclosing element
>
> The since checker verifies that for every API element, the real since value and the effective since value are the same, and reports an error if they are not.
>
> Important note : We only check code written since `JDK 9` as the releases used to determine the expected value of `@since` tags are taken from the historical data built into `javac` which only goes back that far
>
> The intial comment at the beginning of `SinceChecker.java` holds more information into the program.
>
> I already have filed issues and fixed some wrong tags like in #18640, #18032, #18030, #18055, #18373, #18954, #18972.
Various comments inline. Overall, a great start.
Consider adding more explanatory comments for the bigger items, like classes and long/important methods. Imagine a future-person looking over your shoulder, asking what the more important items do or are for.
test/jdk/tools/sincechecker/SinceChecker.java line 49:
> 47: import java.util.regex.Matcher;
> 48: import java.util.regex.Pattern;
> 49: import java.util.stream.Collectors;
The imports are somewhat unordered. I recommend the follooing order
* `java.*` imports first,
* then `javax.*` imports
* then `com.sun.*` imports
* and finally the `jtreg` import.
And, alphabetically sorted in each group.
test/jdk/tools/sincechecker/SinceChecker.java line 52:
> 50:
> 51: /*
> 52: The `@since` checker works as a two-step process:
Insert an initial sentence or paragraph, such as the following:
This checker checks the values of the `@since` tag found in the documentation comment for an element
against the release in which the element first appeared.
The source code containing the documentation comments is read from `src.zip` in the release of
JDK used to run the test. The releases used to determine the expected value of `@since` tags
are taken from the historical data built into `javac`.
test/jdk/tools/sincechecker/SinceChecker.java line 79:
> 77: - When an element is still marked as preview, the `@since` should be the first JDK release where the element was added.
> 78: - If the element is no longer marked as preview, the `@since` should be the first JDK release where it was no longer preview.
> 79:
Add a comment about need for special treatment of early preview features.
test/jdk/tools/sincechecker/SinceChecker.java line 87:
> 85: public class SinceChecker {
> 86: private final Map<String, Set<String>> LEGACY_PREVIEW_METHODS = new HashMap<>();
> 87: private final Map<String, IntroducedIn> classDictionary = new HashMap<>();
See comment lower down about `class IntroducedIn`
test/jdk/tools/sincechecker/SinceChecker.java line 116:
> 114: }
> 115:
> 116: private void processModuleRecord(ModuleElement moduleElement, String releaseVersion, JavacTask ct) {
for this method, and similarly named methods, the use of `Record` seems confusing, if only because there do not seem to be any record classes being processed or analyzed. Consider dropping `Record` or changing it to `Element`. Same for `analyzePackageRecord`, `analyzeClassRecord`.
Also, consider being consistent with `process` or analyze`.
test/jdk/tools/sincechecker/SinceChecker.java line 135:
> 133: private void analyzeClassRecord(TypeElement te, String version, Types types, Elements elements) {
> 134: Set<Modifier> classModifiers = te.getModifiers();
> 135: if (!(classModifiers.contains(Modifier.PUBLIC) || classModifiers.contains(Modifier.PROTECTED))) {
Insert comment:
/// JDK documentation only contains public and protected declarations
which is 99% true. (The serialization page can contain `private` declarations...)
test/jdk/tools/sincechecker/SinceChecker.java line 140:
> 138: persistElement(te.getEnclosingElement(), te, types, version);
> 139: elements.getAllMembers(te).stream()
> 140: .filter(element -> element.getModifiers().contains(Modifier.PUBLIC) || element.getModifiers().contains(Modifier.PROTECTED))
Consider writing and using a predicate method:
boolean isDocumented(Element e) {
var mods = e.getModifiers();
return mods.contains(Modifier,.PUBLIC) || mods.contains(Modifier.PROTECTED);
}
You could then use that method in the obvious way for the class declaration(line 134) and in a lambda as follows:
.filter(this::isDocumented)
test/jdk/tools/sincechecker/SinceChecker.java line 143:
> 141: .filter(element -> element.getKind().isField()
> 142: || element.getKind() == ElementKind.METHOD
> 143: || element.getKind() == ElementKind.CONSTRUCTOR)
Consider writing and using another predicate method,
boolean isMember(Element e) {
var k = e.getKind();
return k.isField() || switch (k) {
case CONSTRUCTOR, METHOD -> true;
default -> false;
};
}
test/jdk/tools/sincechecker/SinceChecker.java line 178:
> 176: }
> 177:
> 178: private void testThisModule(String moduleName) throws Exception {
The word `this` is a bit distracting/unnecessary here. It's enough to say `testModule(String moduleName)` although `checkModule(String moduleName)` would be even better
test/jdk/tools/sincechecker/SinceChecker.java line 190:
> 188: if (!f.exists() && !f.isDirectory()) {
> 189: throw new SkippedException("Skipping Test because src.zip wasn't found");
> 190: }
For modern/new code I would recommend using `java.nio.file.Path` instead of `java.io.File`.
I see you actually have a `Path` in `srcZip`, so use `Files.exists(srcZip)` and `Files.isDirectory(srcZip)`
test/jdk/tools/sincechecker/SinceChecker.java line 214:
> 212: }
> 213: if (!wrongTagsList.isEmpty()) {
> 214: throw new Exception(wrongTagsList.toString());
This looks like it might be the exception to end the test, right, and the wrongTags could be long.
I'd suggest writing a heading to `System.err`, then`wrongTagsList.forEach(System.err::println);` and then throw the exception with a summary string, like `throw new Exception(wrongTagsList.size() + " problems found");
test/jdk/tools/sincechecker/SinceChecker.java line 223:
> 221: private void processModuleCheck(ModuleElement moduleElement, JavacTask ct, Path packagePath, EffectiveSourceSinceHelper javadocHelper) {
> 222: if (moduleElement == null) {
> 223: wrongTagsList.add("Module element: was null because `elements.getModuleElement(moduleName)` returns null." +
This doesn't look like a "Wrong tag" so much as a general error message.
Consider this as a different paradigm -- instead of saving up strings in `wrongTagsList`, consider having a method to report (and count) error messages as you find them:
private int errorCount;
void error(String message) {
System.err.println(message);
errorCount++;
}
then at the end of `testThisModule`, just check if `errorCount>0` to decide whether to throw the exception.
Note, in this model, you need not have a `\n` at the end of every string argument, the way you do for add strings added into `wrongTagsList`.
test/jdk/tools/sincechecker/SinceChecker.java line 274:
> 272: try {
> 273: byte[] packageAsBytes = Files.readAllBytes(pkgInfo);
> 274: String packageContent = new String(packageAsBytes, StandardCharsets.UTF_8);
Easier to just use `Files.readString`
test/jdk/tools/sincechecker/SinceChecker.java line 309:
> 307: .map(TypeElement.class::cast)
> 308: .forEach(nestedClass -> analyzeClassCheck(nestedClass, currentjdkVersion, javadocHelper, types, elementUtils));
> 309: }
more possible applications of those utility predicate methods I mentioned earlier.
test/jdk/tools/sincechecker/SinceChecker.java line 330:
> 328: mappedVersion.introducedStable;
> 329: } catch (Exception e) {
> 330: wrongTagsList.add("For element " + element + "mappedVersion" + mappedVersion + " is null" + e + "\n");
Put a space e or colon between `is null` and `e`
test/jdk/tools/sincechecker/SinceChecker.java line 358:
> 356: private void checkEquals(String sinceVersion, String mappedVersion, String id) {
> 357: if (sinceVersion == null || mappedVersion == null) {
> 358: wrongTagsList.add("For " + id + " NULL value for either mapped or real `@since` . mapped version is="
Here and elsewhere, instead of `"For " + name + ....` consider using the format `name + ": " + ...` for the format of the messages
test/jdk/tools/sincechecker/SinceChecker.java line 375:
> 373: if (mappedVersion.equals("9")) {
> 374: message = "For " + elementSimpleName +
> 375: " Wrong `@since` version " + sinceVersion + " But the element exists before JDK 10\n";
lower case "but"
test/jdk/tools/sincechecker/SinceChecker.java line 421:
> 419: public String introducedPreview;
> 420: public String introducedStable;
> 421: }
Suggest move this nearer the map at the top of the class -- either move this class up, or those members down.
test/jdk/tools/sincechecker/SinceChecker.java line 423:
> 421: }
> 422:
> 423: //these were preview in before the introduction of the @PreviewFeature
Just curious: where do you find this information?
test/jdk/tools/sincechecker/SinceChecker.java line 543:
> 541: }
> 542:
> 543: private final class EffectiveSourceSinceHelper implements AutoCloseable {
Consider putting a doc comment describing the purpose of this class. The name hints at something useful, but is not specific enough by itself.
test/jdk/tools/sincechecker/SinceChecker.java line 559:
> 557: fm.close();
> 558: } catch (IOException closeEx) {
> 559: }
Consider using `ex.addSuppressed(closeEx);` instead of just dropping the exception
test/jdk/tools/sincechecker/SinceChecker.java line 716:
> 714: }
> 715:
> 716: private static final class PatchModuleFileManager
provide a short doc comment describing the purpose/use of this class
test/jdk/tools/sincechecker/testjavabase/SinceChecker.java line 37:
> 35:
> 36: public class SinceChecker {
> 37: }
You don't need this class, do you?
The comment should be enough to run the validator
-------------
PR Review: https://git.openjdk.org/jdk/pull/18934#pullrequestreview-2025970944
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578567066
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578573782
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578579654
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578592510
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578583705
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578584959
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578587585
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578588555
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578601911
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578593616
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578597558
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578600689
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578604639
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578605090
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578605648
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581605988
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581605289
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578592255
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581606328
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581603329
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581605077
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1581604207
PR Review Comment: https://git.openjdk.org/jdk/pull/18934#discussion_r1578057928
More information about the core-libs-dev
mailing list