Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bypass AST model -> HTML Conversion for Markdown Comments #3332

Merged
merged 2 commits into from
Dec 12, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,35 @@

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MemberRef;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.MethodRefParameter;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.manipulation.internal.javadoc.CoreJavaDocSnippetStringEvaluator;
Expand All @@ -38,6 +53,8 @@
import org.eclipse.jdt.core.manipulation.internal.javadoc.CoreMarkdownAccessImpl;
import org.eclipse.jdt.core.manipulation.internal.javadoc.IJavadocContentFactory;
import org.eclipse.jdt.core.manipulation.internal.javadoc.JavadocLookup;
import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.ui.viewsupport.CoreJavaElementLinks;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
Expand Down Expand Up @@ -70,18 +87,167 @@ public static Reader getPlainTextContentReader(IMember member) throws JavaModelE

public static Reader getMarkdownContentReader(IJavaElement element) {

CoreJavadocAccess access = createJdtLsJavadocAccess();
try {
CoreJavadocAccess access = createJdtLsJavadocAccess();
String rawHtml = access.getHTMLContent(element, true);
Reader markdownReader = new JavaDoc2MarkdownConverter(rawHtml).getAsReader();
return markdownReader;
String content = getJavaDocNode(element);
if (content != null && content.startsWith("///")) {
Javadoc node = CoreJavadocContentAccessUtility.getJavadocNode(element, content);
Map<String, List<TagElement>> javadocTags = new HashMap<>();
StringBuilder buf = new StringBuilder();
for (Object obj : node.tags()) {
TagElement tag = (TagElement) obj;
if (tag.getTagName() != null) {
javadocTags.computeIfAbsent(tag.getTagName(), k -> new ArrayList<>()).add((tag));
} else {
buf.append("\n");
collectTagElements(content, element, tag, buf);
}
}

for (Map.Entry<String, List<TagElement>> entry : javadocTags.entrySet()) {
String tagName = entry.getKey();

String heading = switch (tagName) {
case TagElement.TAG_API_NOTE -> "API Note:";
case TagElement.TAG_AUTHOR -> "Author:";
case TagElement.TAG_IMPL_SPEC -> "Impl Spec:";
case TagElement.TAG_IMPL_NOTE -> "Impl Note:";
case TagElement.TAG_PARAM -> "Parameters:";
case TagElement.TAG_PROVIDES -> "Provides:";
case TagElement.TAG_RETURN -> "Returns:";
case TagElement.TAG_THROWS -> "Throws:";
case TagElement.TAG_EXCEPTION -> "Throws:";
case TagElement.TAG_SINCE -> "Since:";
case TagElement.TAG_SEE -> "See:";
case TagElement.TAG_VERSION -> "See:";
case TagElement.TAG_USES -> "Uses:";
default -> "";
};
buf.append("\n");
buf.append("* **" + heading + "**");

for (TagElement tag : entry.getValue()) {
buf.append("\n");
buf.append(" * ");
collectTagElements(content, element, tag, buf);
}
}

return buf.length() > 0 ? new StringReader(buf.substring(1)) : new StringReader(content);
} else {
String rawHtml = access.getHTMLContent(element, true);
Reader markdownReader = new JavaDoc2MarkdownConverter(rawHtml).getAsReader();
return markdownReader;
}
} catch (IOException | CoreException e) {

}

return null;
}

private static void collectTagElements(String content, IJavaElement element, TagElement tag, StringBuilder buf) {
Deque<ASTNode> queue = new LinkedList<>();
queue.addAll(tag.fragments());
while (!queue.isEmpty()) {
ASTNode e = queue.pop();
if (e instanceof TagElement t) {
if ("@link".equals(t.getTagName()) || "@linkplain".equals(t.getTagName())) {
collectLinkedTag(element, t, buf);
} else {
collectTagElements(content, element, t, buf);
}
} else if (e instanceof TextElement) {
buf.append(((TextElement) e).getText());
} else if ("@see".equals(tag.getTagName())) {
collectLinkedTag(element, tag, buf);
} else {
}

ASTNode next = queue.peek();
if (next != null) {
int currEnd = e.getStartPosition() + e.getLength();
int nextStart = next.getStartPosition();
if (currEnd != nextStart) {
if (content.substring(currEnd, nextStart).split("///").length > 2) {
buf.append(" \n");
} else {
buf.append("\n");
}
} else {
buf.append(" ");
}
}
}
}

private static void collectLinkedTag(IJavaElement element, TagElement t, StringBuilder buf) {
List children = t.fragments();
if (t.fragments().size() > 0) {
try {
String[] res;
String linkTitle;
if (t.fragments().size() == 2) {
linkTitle = ((TextElement) t.fragments().get(0)).getText();
res = collectLinkElement((ASTNode) children.get(1));
} else {
res = collectLinkElement((ASTNode) children.get(0));
linkTitle = res[0];
}
buf.append("[" + linkTitle + "]");
String uri = JdtLsJavadocAccessImpl.createLinkURIHelper(CoreJavaElementLinks.JAVADOC_SCHEME, element, res[0], res.length > 1 ? res[1] : null,
res.length > 2 ? Arrays.asList(res).subList(2, res.length).toArray(new String[0]) : null);
buf.append("(" + uri + ")");
} catch (URISyntaxException ex) {
JavaManipulationPlugin.log(ex);
}
}
}

private static String[] collectLinkElement(ASTNode e) {
String refTypeName = null;
String refMemberName = null;
String[] refMethodParamTypes = null;
String[] refMethodParamNames = null;
if (e instanceof Name) {
Name name = (Name) e;
refTypeName = name.getFullyQualifiedName();
} else if (e instanceof MemberRef) {
MemberRef memberRef = (MemberRef) e;
Name qualifier = memberRef.getQualifier();
refTypeName = qualifier == null ? "" : qualifier.getFullyQualifiedName(); //$NON-NLS-1$
refMemberName = memberRef.getName().getIdentifier();
} else if (e instanceof MethodRef) {
MethodRef methodRef = (MethodRef) e;
Name qualifier = methodRef.getQualifier();
refTypeName = qualifier == null ? "" : qualifier.getFullyQualifiedName(); //$NON-NLS-1$
refMemberName = methodRef.getName().getIdentifier();
List<MethodRefParameter> params = methodRef.parameters();
int ps = params.size();
refMethodParamTypes = new String[ps];
refMethodParamNames = new String[ps];
for (int i = 0; i < ps; i++) {
MethodRefParameter param = params.get(i);
refMethodParamTypes[i] = ASTNodes.asString(param.getType());
SimpleName paramName = param.getName();
if (paramName != null) {
refMethodParamNames[i] = paramName.getIdentifier();
}
}
} else if (e instanceof TextElement) {
refTypeName = ((TextElement) e).getText();
}
List<String> result = new ArrayList<>();
result.add(refTypeName);
if (refMemberName != null) {
result.add(refMemberName);
}
if (refMethodParamTypes != null) {
result.addAll(Arrays.asList(refMethodParamTypes));
}
return result.toArray(new String[0]);
}

/**
* @return
*/
Expand Down Expand Up @@ -139,6 +305,31 @@ public IJavadocAccess createJavadocAccess(IJavaElement element, Javadoc javadoc,
}
};

public static String getJavaDocNode(IJavaElement element) throws JavaModelException {
IMember member;
if (element instanceof ILocalVariable) {
member = ((ILocalVariable) element).getDeclaringMember();
} else if (element instanceof ITypeParameter) {
member = ((ITypeParameter) element).getDeclaringMember();
} else if (element instanceof IMember) {
member = (IMember) element;
} else {
return null;
}

IBuffer buf = member.getOpenable().getBuffer();
if (buf == null) {
return null; // no source attachment found
}

ISourceRange javadocRange = member.getJavadocRange();
if (javadocRange == null) {
return null;
}
String rawJavadoc = buf.getText(javadocRange.getOffset(), javadocRange.getLength());
return rawJavadoc;
}

private static class JdtLsJavadocAccessImpl extends CoreJavadocAccessImpl {

/**
Expand Down Expand Up @@ -323,7 +514,11 @@ protected String markSnippet(String text, boolean isInSnippet) {

@Override
protected String createLinkURI(String scheme, IJavaElement element, String refTypeName, String refMemberName, String[] refParameterTypes) throws URISyntaxException {
URI javadocURI = CoreJavaElementLinks.createURIAsUri(scheme, fElement, refTypeName, refMemberName, refParameterTypes);
return createLinkURIHelper(scheme, fElement, refTypeName, refMemberName, refParameterTypes);
}

public static String createLinkURIHelper(String scheme, IJavaElement element, String refTypeName, String refMemberName, String[] refParameterTypes) throws URISyntaxException {
URI javadocURI = CoreJavaElementLinks.createURIAsUri(scheme, element, refTypeName, refMemberName, refParameterTypes);
IJavaElement linkTarget = CoreJavaElementLinks.parseURI(javadocURI);
if (linkTarget == null) {
return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -780,12 +780,10 @@ public void testHoverMarkdownComment() throws Exception {
assertEquals(2, hover.getContents().getLeft().size());

//@formatter:off
String expectedJavadoc = "## TestClass ##\n"
+ "\n"
+ "Paragraph\n"
+ "\n"
+ " * item 1\n"
+ " * *item 2*";
String expectedJavadoc = "## TestClass \n"
+ "Paragraph \n"
+ "- item 1\n"
+ "- _item 2_";
//@formatter:on
String actual = hover.getContents().getLeft().get(1).getLeft();
actual = ResourceUtils.dos2Unix(actual);
Expand Down
Loading