From 8d3de45f4dfd60dc4e2f210cb0c085fcf6efb8e2 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 4 Jun 2024 11:54:49 +0000 Subject: [PATCH] 8325168: JShell should support Markdown comments Reviewed-by: jjg --- .../share/classes/module-info.java | 2 - .../shellsupport/doc/JavadocFormatter.java | 0 .../shellsupport/doc/JavadocHelper.java | 270 ++++++++++++++++-- .../doc/resources/javadocformatter.properties | 0 src/jdk.jshell/share/classes/module-info.java | 3 +- .../doc/FullJavadocHelperTest.java | 2 +- .../doc/JavadocFormatterTest.java | 4 +- .../shellsupport/doc/JavadocHelperTest.java | 158 +++++++++- 8 files changed, 403 insertions(+), 36 deletions(-) rename src/{jdk.compiler => jdk.jshell}/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java (100%) rename src/{jdk.compiler => jdk.jshell}/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java (77%) rename src/{jdk.compiler => jdk.jshell}/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties (100%) diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 63b5aa63e4c..60a9ae0e476 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -268,8 +268,6 @@ module jdk.compiler { jdk.javadoc, jdk.jshell, jdk.internal.md; - exports jdk.internal.shellsupport.doc to - jdk.jshell; uses javax.annotation.processing.Processor; uses com.sun.source.util.Plugin; diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java similarity index 100% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocFormatter.java diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java similarity index 77% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java index 9839f79c1dc..e69f32097b2 100644 --- a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, 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 @@ -43,8 +43,10 @@ import java.util.Objects; import java.util.Set; import java.util.Stack; import java.util.TreeMap; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -57,6 +59,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; +import javax.lang.model.util.Elements.DocCommentKind; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; @@ -70,12 +73,15 @@ import javax.tools.ToolProvider; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.InheritDocTree; +import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.ParamTree; +import com.sun.source.doctree.RawTextTree; import com.sun.source.doctree.ReturnTree; import com.sun.source.doctree.ThrowsTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import com.sun.source.util.DocSourcePositions; import com.sun.source.util.DocTreePath; @@ -91,6 +97,12 @@ import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.Pair; +import jdk.internal.org.commonmark.ext.gfm.tables.TablesExtension; +import jdk.internal.org.commonmark.node.Node; +import jdk.internal.org.commonmark.parser.IncludeSourceSpans; +import jdk.internal.org.commonmark.parser.Parser; +import jdk.internal.org.commonmark.renderer.html.HtmlRenderer; + /**Helper to find javadoc and resolve @inheritDoc. */ public abstract class JavadocHelper implements AutoCloseable { @@ -222,16 +234,18 @@ public abstract class JavadocHelper implements AutoCloseable { if (docComment == null) return null; - Pair parsed = parseDocComment(task, docComment); + DocCommentKind docCommentKind = trees.getDocCommentKind(el); + Pair parsed = parseDocComment(task, docComment, docCommentKind); DocCommentTree docCommentTree = parsed.fst; int offset = parsed.snd; IOException[] exception = new IOException[1]; Comparator spanComp = (span1, span2) -> span1[0] != span2[0] ? span2[0] - span1[0] - : span2[1] - span1[0]; + : span2[1] - span1[1]; //spans in the docComment that should be replaced with the given Strings: Map> replace = new TreeMap<>(spanComp); - DocSourcePositions sp = trees.getSourcePositions(); + SyntheticAwareTreeDocSourcePositions sp = + new SyntheticAwareTreeDocSourcePositions(trees.getSourcePositions()); //fill in missing elements and resolve {@inheritDoc} //if an element is (silently) missing in the javadoc, a synthetic {@inheritDoc} @@ -252,6 +266,29 @@ public abstract class JavadocHelper implements AutoCloseable { private Map syntheticTrees = new IdentityHashMap<>(); /* Position on which the synthetic trees should be inserted.*/ private long insertPos = offset; + @Override + public Void scan(Iterable nodes, Void p) { + if (nodes != null && containsMarkdown(nodes)) { + JoinedMarkdown joinedMarkdowns = joinMarkdown(sp, dcTree, nodes); + String source = joinedMarkdowns.source(); + Parser parser = Parser.builder() + .extensions(List.of(TablesExtension.create())) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + Node document = parser.parse(source); + String htmlWithPlaceHolders = stripParagraphs(HtmlRenderer.builder() + .build() + .render(document)); + + for (String part : htmlWithPlaceHolders.split(PLACEHOLDER_PATTERN, -1)) { + int[] replaceSpan = joinedMarkdowns.replaceSpans.remove(0); + + replace.computeIfAbsent(replaceSpan, _ -> new ArrayList<>()) + .add(part); + } + } + return super.scan(nodes, p); + } @Override @DefinedBy(Api.COMPILER_TREE) public Void visitDocComment(DocCommentTree node, Void p) { dcTree = node; @@ -260,21 +297,19 @@ public abstract class JavadocHelper implements AutoCloseable { if (node.getFullBody().isEmpty()) { //there is no body in the javadoc, add synthetic {@inheritDoc}, which //will be automatically filled in visitInheritDoc: - DocCommentTree dc = parseDocComment(task, "{@inheritDoc}").fst; + DocCommentTree dc = parseDocComment(task, "{@inheritDoc}", DocCommentKind.TRADITIONAL).fst; syntheticTrees.put(dc, "*\n"); interestingParent.push(dc); boolean prevInSynthetic = inSynthetic; try { inSynthetic = true; - scan(dc.getFirstSentence(), p); - scan(dc.getBody(), p); + scan(dc.getFullBody(), p); } finally { inSynthetic = prevInSynthetic; interestingParent.pop(); } } else { - scan(node.getFirstSentence(), p); - scan(node.getBody(), p); + scan(node.getFullBody(), p); } //add missing @param, @throws and @return, augmented with {@inheritDoc} //which will be resolved in visitInheritDoc: @@ -401,7 +436,7 @@ public abstract class JavadocHelper implements AutoCloseable { return null; } Pair parsed = - parseDocComment(inheritedJavacTask, inherited); + parseDocComment(inheritedJavacTask, inherited, DocCommentKind.TRADITIONAL); DocCommentTree inheritedDocTree = parsed.fst; int offset = parsed.snd; List> inheritedText = new ArrayList<>(); @@ -478,6 +513,21 @@ public abstract class JavadocHelper implements AutoCloseable { } return super.visitInheritDoc(node, p); } + @Override + public Void visitLink(LinkTree node, Void p) { + if (sp.isRewrittenTree(null, dcTree, node)) { + //this link is a synthetic rewritten link, replace + //the original span with the new link: + int start = (int) sp.getStartPosition(null, dcTree, node); + int end = (int) sp.getEndPosition(null, dcTree, node); + + replace.computeIfAbsent(new int[] {start, end}, _ -> new ArrayList<>()) + .add(node.toString()); + + return null; + } + return super.visitLink(node, p); + } private boolean inSynthetic; @Override @DefinedBy(Api.COMPILER_TREE) public Void scan(DocTree tree, Void p) { @@ -552,7 +602,10 @@ public abstract class JavadocHelper implements AutoCloseable { tags.add(toInsert); } - private final List tagOrder = Arrays.asList(DocTree.Kind.PARAM, DocTree.Kind.THROWS, DocTree.Kind.RETURN); + private static final List tagOrder = + Arrays.asList(DocTree.Kind.PARAM, DocTree.Kind.THROWS, + DocTree.Kind.RETURN, DocTree.Kind.SEE, + DocTree.Kind.SINCE); }.scan(docCommentTree, null); if (replace.isEmpty()) @@ -604,25 +657,38 @@ public abstract class JavadocHelper implements AutoCloseable { } private DocTree parseBlockTag(JavacTask task, String blockTag) { - DocCommentTree dc = parseDocComment(task, blockTag).fst; + DocCommentTree dc = parseDocComment(task, blockTag, DocCommentKind.TRADITIONAL).fst; return dc.getBlockTags().get(0); } - private Pair parseDocComment(JavacTask task, String javadoc) { + private Pair parseDocComment(JavacTask task, String javadoc, DocCommentKind docCommentKind) { DocTrees trees = DocTrees.instance(task); try { - SimpleJavaFileObject fo = - new SimpleJavaFileObject(new URI("mem://doc.html"), Kind.HTML) { - @Override @DefinedBy(Api.COMPILER) - public CharSequence getCharContent(boolean ignoreEncodingErrors) - throws IOException { - return "" + javadoc + ""; - } - }; + URI uri; + Kind kind; + String content; + int offset; + if (docCommentKind == DocCommentKind.TRADITIONAL) { + uri = new URI("mem:///doc.html"); + kind = Kind.HTML; + content = "" + javadoc + ""; + offset = "".length(); + } else { + uri = new URI("mem:///doc.md"); + kind = Kind.OTHER; + content = javadoc; + offset = 0; + } + SimpleJavaFileObject fo = new SimpleJavaFileObject(uri, kind) { + @Override @DefinedBy(Api.COMPILER) + public CharSequence getCharContent(boolean ignoreEncodingErrors) + throws IOException { + return content; + } + }; DocCommentTree tree = trees.getDocCommentTree(fo); - int offset = (int) trees.getSourcePositions().getStartPosition(null, tree, tree); - offset += "".length(); + offset += (int) trees.getSourcePositions().getStartPosition(null, tree, tree); return Pair.of(tree, offset); } catch (URISyntaxException ex) { throw new IllegalStateException(ex); @@ -776,6 +842,164 @@ public abstract class JavadocHelper implements AutoCloseable { fm.close(); } + private static boolean containsMarkdown(Iterable trees) { + return StreamSupport.stream(trees.spliterator(), false) + .anyMatch(t -> t.getKind() == DocTree.Kind.MARKDOWN); + } + + private static final char PLACEHOLDER = '\uFFFC'; // Unicode Object Replacement Character + + private static JoinedMarkdown joinMarkdown(SyntheticAwareTreeDocSourcePositions sp, + DocCommentTree comment, + Iterable trees) { + StringBuilder sourceBuilder = new StringBuilder(); + List replaceSpans = new ArrayList<>(); + int currentSpanStart = (int) sp.getStartPosition(null, comment, trees.iterator().next()); + DocTree lastTree = null; + + for (DocTree tree : trees) { + if (tree instanceof RawTextTree t) { + if (t.getKind() != DocTree.Kind.MARKDOWN) { + throw new IllegalStateException(t.getKind().toString()); + } + String code = t.getContent(); + // handle the (unlikely) case of any U+FFFC characters existing in the code + int start = 0; + int pos; + while ((pos = code.indexOf(PLACEHOLDER, start)) != -1) { + replaceSpans.add(new int[] {currentSpanStart, currentSpanStart + pos - start}); + currentSpanStart += pos - start + 1; + start = pos + 1; + } + sourceBuilder.append(code); + } else { + int treeStart = (int) sp.getStartPosition(null, comment, tree); + int treeEnd = (int) sp.getEndPosition(null, comment, tree); + replaceSpans.add(new int[] {currentSpanStart, treeStart}); + currentSpanStart = treeEnd; + sourceBuilder.append(PLACEHOLDER); + } + lastTree = tree; + } + + int end = (int) sp.getEndPosition(null, comment, lastTree); + + replaceSpans.add(new int[] {currentSpanStart, end}); + + return new JoinedMarkdown(sourceBuilder.toString(), replaceSpans); + } + + private static String stripParagraphs(String input) { + input = input.replace("

", ""); + + if (input.startsWith("

")) { + input = input.substring(3); + } + + if (input.endsWith("\n")) { + input = input.substring(0, input.length() - 1); + } + + return input.replace("

", "\n

"); + } + + private static final String PLACEHOLDER_PATTERN = Pattern.quote("" + PLACEHOLDER); + + private record JoinedMarkdown(String source, List replaceSpans) {} + + //embedded transformers may produce rewritten trees for link, + //there re-written trees has start position -1, the DocSourcePositions + //will provide an adjusted span based on the link nested nodes: + private static final class SyntheticAwareTreeDocSourcePositions implements DocSourcePositions { + + private final DocSourcePositions delegate; + private final Map adjustedSpan = new HashMap<>(); + private final Set rewrittenTrees = new HashSet<>(); + + public SyntheticAwareTreeDocSourcePositions(DocSourcePositions delegate) { + this.delegate = delegate; + } + + @Override + public long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + ensureAdjustedSpansFilled(file, comment, tree); + + long[] adjusted = adjustedSpan.get(tree); + + if (adjusted != null) { + return adjusted[0]; + } + + return delegate.getStartPosition(file, comment, tree); + } + + @Override + public long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + ensureAdjustedSpansFilled(file, comment, tree); + + long[] adjusted = adjustedSpan.get(tree); + + if (adjusted != null) { + return adjusted[1]; + } + + return delegate.getEndPosition(file, comment, tree); + } + + @Override + public long getStartPosition(CompilationUnitTree file, Tree tree) { + return delegate.getStartPosition(file, tree); + } + + @Override + public long getEndPosition(CompilationUnitTree file, Tree tree) { + return delegate.getEndPosition(file, tree); + } + + boolean isRewrittenTree(CompilationUnitTree file, + DocCommentTree comment, + DocTree tree) { + ensureAdjustedSpansFilled(file, comment, tree); + return rewrittenTrees.contains(tree); + } + + private void ensureAdjustedSpansFilled(CompilationUnitTree file, + DocCommentTree comment, + DocTree tree) { + if (tree.getKind() != DocTree.Kind.LINK && + tree.getKind() != DocTree.Kind.LINK_PLAIN) { + return ; + } + + long[] span; + long treeStart = delegate.getStartPosition(file, comment, tree); + + if (treeStart == (-1)) { + LinkTree link = (LinkTree) tree; + Iterable nested = () -> Stream.concat(link.getLabel().stream(), + Stream.of(link.getReference())) + .iterator(); + long start = Long.MAX_VALUE; + long end = Long.MIN_VALUE; + + for (DocTree t : nested) { + start = Math.min(start, + delegate.getStartPosition(file, comment, t)); + end = Math.max(end, + delegate.getEndPosition(file, comment, t)); + } + + span = new long[] {(int) start - 1, (int) end + 1}; + rewrittenTrees.add(tree); + } else { + long treeEnd = delegate.getEndPosition(file, comment, tree); + span = new long[] {treeStart, treeEnd}; + } + + adjustedSpan.put(tree, span); + } + } + private static final class PatchModuleFileManager extends ForwardingJavaFileManager { diff --git a/src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties similarity index 100% rename from src/jdk.compiler/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties rename to src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/resources/javadocformatter.properties diff --git a/src/jdk.jshell/share/classes/module-info.java b/src/jdk.jshell/share/classes/module-info.java index 9266fd0fed5..b1e4270692d 100644 --- a/src/jdk.jshell/share/classes/module-info.java +++ b/src/jdk.jshell/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -71,6 +71,7 @@ module jdk.jshell { requires jdk.compiler; requires jdk.internal.ed; requires jdk.internal.le; + requires jdk.internal.md; requires jdk.internal.opt; requires transitive java.compiler; diff --git a/test/langtools/jdk/internal/shellsupport/doc/FullJavadocHelperTest.java b/test/langtools/jdk/internal/shellsupport/doc/FullJavadocHelperTest.java index 729ac92143f..fcfd40b3292 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/FullJavadocHelperTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/FullJavadocHelperTest.java @@ -28,7 +28,7 @@ * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main - * jdk.compiler/jdk.internal.shellsupport.doc + * jdk.jshell/jdk.internal.shellsupport.doc * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask * @run testng/timeout=900/othervm -Xmx1024m FullJavadocHelperTest */ diff --git a/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java b/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java index 27ce31fa5b6..153c010c33d 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocFormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -26,7 +26,7 @@ * @bug 8131019 8169561 8261450 * @summary Test JavadocFormatter * @library /tools/lib - * @modules jdk.compiler/jdk.internal.shellsupport.doc + * @modules jdk.jshell/jdk.internal.shellsupport.doc * @run testng JavadocFormatterTest */ diff --git a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java index 55d258788fe..e82553a4cfe 100644 --- a/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java +++ b/test/langtools/jdk/internal/shellsupport/doc/JavadocHelperTest.java @@ -28,7 +28,7 @@ * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main - * jdk.compiler/jdk.internal.shellsupport.doc + * jdk.jshell/jdk.internal.shellsupport.doc * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask * @run testng JavadocHelperTest * @key randomness @@ -93,12 +93,6 @@ public class JavadocHelperTest { " @return value\n"); } - private Element getFirstMethod(JavacTask task, String typeName) { - return ElementFilter.methodsIn(task.getElements().getTypeElement(typeName).getEnclosedElements()).get(0); - } - - private Function getSubTest = t -> getFirstMethod(t, "test.Sub"); - public void testInheritNoJavadoc() throws Exception { doTestJavadoc("", getSubTest, @@ -301,6 +295,150 @@ public class JavadocHelperTest { "@return value\n"); } + public void testMarkdown() throws Exception { + doTestJavadoc(""" + /// Prefix {@inheritDoc} suffix. + /// + /// *Another* __paragraph__. + /// + /// Paragraph \ufffc with \ufffc replacement \ufffc character. + /// + /// @param p1 prefix {@inheritDoc} suffix + /// @param p2 prefix {@inheritDoc} suffix + /// @param p3 prefix {@inheritDoc} suffix + /// @throws IllegalStateException prefix {@inheritDoc} suffix + /// @throws IllegalArgumentException prefix {@inheritDoc} suffix + /// @throws IllegalAccessException prefix {@inheritDoc} suffix + /// @return prefix {@inheritDoc} suffix + """, + getSubTest, + """ + Prefix javadoc1 suffix. + +

Another paragraph. + +

Paragraph \ufffc with \ufffc replacement \ufffc character. + + @param p1 prefix param1 suffix + @param p2 prefix param2 suffix + @param p3 prefix param3 suffix + @throws IllegalStateException prefix exc1 suffix + @throws IllegalArgumentException prefix exc2 suffix + @throws IllegalAccessException prefix exc3 suffix + @return prefix value suffix"""); + } + + public void testMarkdown2() throws Exception { + doTestJavadoc(""" + /// {@inheritDoc} + /// + /// *Another* __paragraph__. [java.lang.Object] + /// + /// @since snc + """, + getSubTest, + """ + javadoc1 + +

Another paragraph. {@link java.lang.Object} + + @param p1 param1 + @param p2 param2 + @param p3 param3 + @throws java.lang.IllegalStateException exc1 + @throws java.lang.IllegalArgumentException exc2 + @throws java.lang.IllegalAccessException exc3 + @return value + @since snc"""); + } + + public void testMarkdown3() throws Exception { + doTestJavadoc(""" + /// {@inheritDoc} + /// + /// *Another* __paragraph__. + """, + getSubTest, + //the formatting could be improved: + """ + javadoc1 + +

Another paragraph.@param p1 param1 + @param p2 param2 + @param p3 param3 + @throws java.lang.IllegalStateException exc1 + @throws java.lang.IllegalArgumentException exc2 + @throws java.lang.IllegalAccessException exc3 + @return value + """); + } + + public void testMarkdown4() throws Exception { + doTestJavadoc(""" + /// {@inheritDoc} + /// + /// *Another* __paragraph__. [test][java.lang.Object] + /// + /// @since snc + """, + getSubTest, + """ + javadoc1 + +

Another paragraph. {@linkplain java.lang.Object test} + + @param p1 param1 + @param p2 param2 + @param p3 param3 + @throws java.lang.IllegalStateException exc1 + @throws java.lang.IllegalArgumentException exc2 + @throws java.lang.IllegalAccessException exc3 + @return value + @since snc"""); + } + + public void testMarkdown5() throws Exception { + doTestJavadoc(""" + ///[define classes][java.lang.invoke.MethodHandles.Lookup#defineClass(byte\\[\\])] + /// + /// @since snc + """, + getSubTest, + """ + {@linkplain java.lang.invoke.MethodHandles.Lookup#defineClass(byte[]) define classes} + + @param p1 param1 + @param p2 param2 + @param p3 param3 + @throws java.lang.IllegalStateException exc1 + @throws java.lang.IllegalArgumentException exc2 + @throws java.lang.IllegalAccessException exc3 + @return value + @since snc"""); + } + + public void testMarkdown6() throws Exception { + doTestJavadoc(""" + ///Text1 [define classes][java.lang.invoke.MethodHandles.Lookup#defineClass(byte\\[\\])] + ///text2 + /// + /// @since snc + """, + getSubTest, + """ + Text1 {@linkplain java.lang.invoke.MethodHandles.Lookup#defineClass(byte[]) define classes} + text2 + + @param p1 param1 + @param p2 param2 + @param p3 param3 + @throws java.lang.IllegalStateException exc1 + @throws java.lang.IllegalArgumentException exc2 + @throws java.lang.IllegalAccessException exc3 + @return value + @since snc"""); + } + private void doTestJavadoc(String origJavadoc, Function getElement, String expectedJavadoc) throws Exception { doTestJavadoc(origJavadoc, " /**\n" + @@ -370,6 +508,12 @@ public class JavadocHelperTest { } } + private Element getFirstMethod(JavacTask task, String typeName) { + return ElementFilter.methodsIn(task.getElements().getTypeElement(typeName).getEnclosedElements()).get(0); + } + + private Function getSubTest = t -> getFirstMethod(t, "test.Sub"); + private static final class JFOImpl extends SimpleJavaFileObject { private final String code;