Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = 'org.moddingx'
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
java.toolchain.languageVersion = JavaLanguageVersion.of(24)

repositories {
mavenCentral()
Expand All @@ -14,7 +14,9 @@ dependencies {
implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0'
implementation 'org.apache.commons:commons-text:1.12.0'
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'org.jsoup:jsoup:1.17.2'
implementation 'org.jsoup:jsoup:1.17.2'
implementation 'org.commonmark:commonmark:0.24.0'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.24.0'
}

task fatjar(type: Jar) {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public static Optional<DocData> from(DocEnv env, Element element) {
DocCommentTree tree = env.docs().getDocCommentTree(elemPath);
if (tree == null) return Optional.empty();
DocTreePath basePath = DocTreePath.getPath(elemPath, tree, tree);
String summary = HtmlConverter.asDocHtml(env, basePath, tree.getFirstSentence());
String text = HtmlConverter.asDocHtml(env, basePath, tree.getFullBody());
String summary = HtmlConverter.asDocHtml(env, element, basePath, tree.getFirstSentence());
String text = HtmlConverter.asDocHtml(env, element, basePath, tree.getFullBody());
List<DocBlockData> properties = tree.getBlockTags().stream()
.flatMap(tag -> DocBlockData.from(env, DocTreePath.getPath(basePath, tag), tag).stream())
.flatMap(tag -> DocBlockData.from(env, element, DocTreePath.getPath(basePath, tag), tag).stream())
.toList();
List<DocBlockData> inlineProperties = DocBlockData.fromInline(env, basePath, properties, tree.getFullBody());
List<DocBlockData> inlineProperties = DocBlockData.fromInline(env, element, basePath, properties, tree.getFullBody());
return Optional.of(new DocData(summary, text, Stream.concat(properties.stream(), inlineProperties.stream()).toList()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private static Optional<String> getParamDoc(DocEnv env, String name, Element ele
for (DocTree block : tree.getBlockTags()) {
if (block.getKind() == DocTree.Kind.PARAM && block instanceof ParamTree pt) {
if (!pt.isTypeParameter() && name.equals(pt.getName().getName().toString())) {
return Optional.of(HtmlConverter.asDocHtml(env, DocTreePath.getPath(basePath, pt), pt.getDescription()));
return Optional.of(HtmlConverter.asDocHtml(env, element, DocTreePath.getPath(basePath, pt), pt.getDescription()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.moddingx.java_doclet_meta.DocEnv;
import org.moddingx.java_doclet_meta.util.HtmlConverter;

import javax.lang.model.element.Element;
import java.util.*;
import java.util.stream.Collectors;

Expand All @@ -23,7 +24,7 @@ default JsonObject json() {

void addProperties(JsonObject json);

static List<DocBlockData> fromInline(DocEnv env, DocTreePath basePath, List<DocBlockData> blocks, List<? extends DocTree> inline) {
static List<DocBlockData> fromInline(DocEnv env, Element context, DocTreePath basePath, List<DocBlockData> blocks, List<? extends DocTree> inline) {
// Inline return tags in the main description should act as separate block tags.
Set<DocBlockData.Type> knownTypes = new HashSet<>(blocks.stream().map(DocBlockData::type).collect(Collectors.toUnmodifiableSet()));
List<DocBlockData> inlineBlocks = new ArrayList<>();
Expand All @@ -32,7 +33,7 @@ static List<DocBlockData> fromInline(DocEnv env, DocTreePath basePath, List<DocB
@Override
public Void visitReturn(ReturnTree tree, Void unused) {
if (tree.isInline() && knownTypes.add(Type.RETURN)) {
inlineBlocks.add(new TextBlock(Type.RETURN, HtmlConverter.asDocHtml(env, DocTreePath.getPath(basePath, tree), tree.getDescription())));
inlineBlocks.add(new TextBlock(Type.RETURN, HtmlConverter.asDocHtml(env, context, DocTreePath.getPath(basePath, tree), tree.getDescription())));
}
return super.visitReturn(tree, unused);
}
Expand All @@ -42,33 +43,33 @@ public Void visitReturn(ReturnTree tree, Void unused) {
return List.copyOf(inlineBlocks);
}

static Optional<DocBlockData> from(DocEnv env, DocTreePath path, DocTree tree) {
static Optional<DocBlockData> from(DocEnv env, Element context, DocTreePath path, DocTree tree) {
// Ignore parameters, they are merged with ParamData
return Optional.ofNullable(switch (tree.getKind()) {
case AUTHOR -> new TextBlock(Type.AUTHOR, HtmlConverter.asDocHtml(env, path, ((AuthorTree) tree).getName()));
case DEPRECATED -> new TextBlock(Type.DEPRECATED, HtmlConverter.asDocHtml(env, path, ((DeprecatedTree) tree).getBody()));
case AUTHOR -> new TextBlock(Type.AUTHOR, HtmlConverter.asDocHtml(env, context, path, ((AuthorTree) tree).getName()));
case DEPRECATED -> new TextBlock(Type.DEPRECATED, HtmlConverter.asDocHtml(env, context, path, ((DeprecatedTree) tree).getBody()));
case EXCEPTION -> {
ThrowsTree ex = (ThrowsTree) tree;
yield ClassTextBlock.from(env, Type.EXCEPTION, path, ex.getExceptionName(), HtmlConverter.asDocHtml(env, path, ex.getDescription()));
yield ClassTextBlock.from(env, Type.EXCEPTION, path, ex.getExceptionName(), HtmlConverter.asDocHtml(env, context, path, ex.getDescription()));
}
case THROWS -> {
ThrowsTree ex = (ThrowsTree) tree;
yield ClassTextBlock.from(env, Type.THROWS, path, ex.getExceptionName(), HtmlConverter.asDocHtml(env, path, ex.getDescription()));
yield ClassTextBlock.from(env, Type.THROWS, path, ex.getExceptionName(), HtmlConverter.asDocHtml(env, context, path, ex.getDescription()));
}
case PROVIDES -> {
ProvidesTree provides = (ProvidesTree) tree;
yield ClassTextBlock.from(env, Type.PROVIDES, path, provides.getServiceType(), HtmlConverter.asDocHtml(env, path, provides.getDescription()));
yield ClassTextBlock.from(env, Type.PROVIDES, path, provides.getServiceType(), HtmlConverter.asDocHtml(env, context, path, provides.getDescription()));

}
case USES -> {
UsesTree provides = (UsesTree) tree;
yield ClassTextBlock.from(env, Type.USES, path, provides.getServiceType(), HtmlConverter.asDocHtml(env, path, provides.getDescription()));
yield ClassTextBlock.from(env, Type.USES, path, provides.getServiceType(), HtmlConverter.asDocHtml(env, context, path, provides.getDescription()));

}
case RETURN -> new TextBlock(Type.RETURN, HtmlConverter.asDocHtml(env, path, ((ReturnTree) tree).getDescription()));
case SERIAL -> new TextBlock(Type.SERIAL, HtmlConverter.asDocHtml(env, path, ((SerialTree) tree).getDescription()));
case SINCE -> new TextBlock(Type.SINCE, HtmlConverter.asDocHtml(env, path, ((SinceTree) tree).getBody()));
case UNKNOWN_BLOCK_TAG -> new TextBlock(Type.UNKNOWN, HtmlConverter.asDocHtml(env, path, ((UnknownBlockTagTree) tree).getContent()));
case RETURN -> new TextBlock(Type.RETURN, HtmlConverter.asDocHtml(env, context, path, ((ReturnTree) tree).getDescription()));
case SERIAL -> new TextBlock(Type.SERIAL, HtmlConverter.asDocHtml(env, context, path, ((SerialTree) tree).getDescription()));
case SINCE -> new TextBlock(Type.SINCE, HtmlConverter.asDocHtml(env, context, path, ((SinceTree) tree).getBody()));
case UNKNOWN_BLOCK_TAG -> new TextBlock(Type.UNKNOWN, HtmlConverter.asDocHtml(env, context, path, ((UnknownBlockTagTree) tree).getContent()));
default -> null;
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.moddingx.java_doclet_meta.util;

import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.RawTextTree;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.Heading;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Entities;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DocTreePreprocessor {

// See jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter$MarkdownHandler
private static final Parser PARSER = Parser.builder().extensions(List.of(TablesExtension.create())).build();
private static final HtmlRenderer RENDERER = HtmlRenderer.builder().omitSingleParagraphP(true).extensions(List.of(TablesExtension.create())).build();
private static final Pattern REPLACEMENT_PATTERN = Pattern.compile("<!--\uFFFC(\\d+)-->");

public static List<ProcessedDocTree> process(Element context, List<? extends DocTree> textElems) {
if (textElems.stream().noneMatch(tree -> tree.getKind() == DocTree.Kind.MARKDOWN)) {
return textElems.stream().<ProcessedDocTree>map(WrappedDocTree::new).toList();
}
StringBuilder markdown = new StringBuilder();
List<ProcessedDocTree> replacements = new ArrayList<>();
for (DocTree tree : textElems) {
if (tree.getKind() == DocTree.Kind.MARKDOWN) {
String markdownContent = ((RawTextTree) tree).getContent();
Matcher m = REPLACEMENT_PATTERN.matcher(markdownContent);
int start = 0;
while (m.find()) {
markdown.append(markdownContent, start, m.start());
int replacementIdx = replacements.size();
replacements.add(new RawHtml(m.group()));
markdown.append("<!--\uFFFC").append(replacementIdx).append("-->");
start = m.end();
}
markdown.append(markdownContent.substring(start));
} else {
int replacementIdx = replacements.size();
replacements.add(new WrappedDocTree(tree));
markdown.append("<!--\uFFFC").append(replacementIdx).append("-->");
}
}

Node node = PARSER.parse(markdown.toString());
adjustHeadings(context, node);
String htmlText = minifyHtml(RENDERER.render(node));
return replaceElements(htmlText, Collections.unmodifiableList(replacements));
}

private static void adjustHeadings(Element context, Node markdown) {
// See jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter$MarkdownHandler$HeadingNodeRenderer
ElementKind kind = context.getKind();
int headingInset = kind.isField() || kind.isExecutable() ? 3 : kind != ElementKind.OTHER ? 1 : 0;

markdown.accept(new AbstractVisitor() {

@Override
public void visit(Heading heading) {
heading.setLevel(Math.min(heading.getLevel() + headingInset, 6));
super.visit(heading);
}
});
}

private static String minifyHtml(String html) {
Document document = Jsoup.parseBodyFragment(html);
document.outputSettings(new Document.OutputSettings()
.syntax(Document.OutputSettings.Syntax.html)
.escapeMode(Entities.EscapeMode.base)
.charset(StandardCharsets.UTF_8)
.prettyPrint(true)
.indentAmount(0)
.maxPaddingWidth(-1)
.outline(false)
);
return document.body().html().strip();
}

private static List<ProcessedDocTree> replaceElements(String htmlText, List<ProcessedDocTree> replacements) {
List<ProcessedDocTree> replacedText = new ArrayList<>(2 * replacements.size() + 1);
Matcher m = REPLACEMENT_PATTERN.matcher(htmlText);
int start = 0;
while (m.find()) {
replacedText.add(new RawHtml(htmlText.substring(start, m.start())));
int replacementIdx = -1;
try {
replacementIdx = Integer.parseInt(m.group(1));
} catch (NumberFormatException e) {
//
}
if (replacementIdx >= 0 && replacementIdx < replacements.size()) {
replacedText.add(replacements.get(replacementIdx));
}
start = m.end();
}
replacedText.add(new RawHtml(htmlText.substring(start)));

List<ProcessedDocTree> result = new ArrayList<>(replacedText.size());
for (ProcessedDocTree tree : replacedText) {
if (tree instanceof RawHtml(String html) && html.isEmpty()) continue;
if (tree instanceof RawHtml(String html2) && !result.isEmpty() && result.getLast() instanceof RawHtml(String html1)) {
result.set(result.size() - 1, new RawHtml(html1 + html2));
} else {
result.add(tree);
}
}
return List.copyOf(result);
}

public sealed interface ProcessedDocTree permits RawHtml, WrappedDocTree {}
public record RawHtml(String html) implements ProcessedDocTree {}
public record WrappedDocTree(DocTree tree) implements ProcessedDocTree {}
}
Loading