From 2688bf7305ff323e02636bfcc37497ebd1563ab5 Mon Sep 17 00:00:00 2001 From: Vladimir Petko Date: Wed, 3 Jun 2026 06:35:57 +0000 Subject: [PATCH] 8385738: Javadoc does not produce reproducible output due to the snippet ids Reviewed-by: nbenalla --- .../formats/html/taglets/SnippetTaglet.java | 8 +- .../doclet/testSnippetTag/TestSnippetTag.java | 95 ++++++++++++++++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index 38baa7b2826..b666a14fd95 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,7 @@ import com.sun.source.util.DocTreePath; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyles; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Action; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.ParseException; @@ -124,7 +125,8 @@ public class SnippetTaglet extends BaseTaglet { if (id != null && !id.isBlank()) { pre.put(HtmlAttr.ID, id); } else { - pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, ids).name()); + var set = ids.computeIfAbsent(tagletWriter.htmlWriter, _ -> new HashSet<>()); + pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, set).name()); } var code = HtmlTree.CODE() .addUnchecked(Text.EMPTY); // Make sure the element is always rendered @@ -207,7 +209,7 @@ public class SnippetTaglet extends BaseTaglet { return snippetContainer.add(pre.add(code)); } - private final Set ids = new HashSet<>(); + private final HashMap> ids = new HashMap<>(); private static final class BadSnippetException extends Exception { diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java index 26030629738..24a7b5aa5da 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8266666 8275788 8276964 8299080 8276966 + * @bug 8266666 8275788 8276964 8299080 8276966 8385738 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -39,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -2685,4 +2686,94 @@ public class TestSnippetTag extends SnippetTester { """); checkNoCrashes(); } + + @Test + public void testSnippetIdCounterResetsPerPage(Path base) throws IOException { + Path src = base.resolve("src"); + + tb.writeJavaFiles(src, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ACustomAnnotation { + } + """, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ZCustomAnnotation { + } + """, + """ + package p; + /** Class A. */ + public class A { + /** + * First snippet on A.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on A.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """, + """ + package p; + /** Class B. */ + public class B { + /** + * First snippet on B.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on B.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """); + + javadoc("-d", base.resolve("out").toString(), + "-use", + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + for (String cls : new String[] { + "A.html", + "B.html", + "class-use/ACustomAnnotation.html", + "class-use/ZCustomAnnotation.html"}) { + var file = base + .resolve("out") + .resolve("p") + .resolve(cls); + String content = Files.readString(file); + for (var snippetId : new String[] {"snippet-foo()1", "snippet-foo()2"}) { + checking("id \"" + snippetId + "\" present in " + cls); + if (content.contains("id=\"" + snippetId + "\"")) { + passed("found"); + } else { + failed("" + snippetId + " not found in " + + String.join("\n", Arrays.asList(content.split("\n")) + .stream() + .filter(l -> l.contains("pre class=\"snippet\"")) + .toList())); + } + } + } + } }