javadoc: show short annotation names in signatures and full names with explanations underneath (IDEA-167076, IDEA-166361)

This commit is contained in:
peter
2017-06-03 23:27:11 +02:00
parent 993c5eca94
commit d82927a096
7 changed files with 186 additions and 55 deletions
@@ -66,37 +66,40 @@ class AnnotationDocGenerator {
return Flow.class.getName().equals(annoQName);
}
void generateAnnotation(StringBuilder buffer, boolean generateLink, boolean useShortNames) {
boolean inferred = AnnotationUtil.isInferredAnnotation(myAnnotation);
boolean external = AnnotationUtil.isExternalAnnotation(myAnnotation);
boolean isExternal() {
return AnnotationUtil.isExternalAnnotation(myAnnotation);
}
boolean isInferred() {
return AnnotationUtil.isInferredAnnotation(myAnnotation);
}
void generateAnnotation(StringBuilder buffer, AnnotationFormat format) {
String qualifiedName = myAnnotation.getQualifiedName();
if (myTargetClass != null &&
qualifiedName != null && JavaDocUtil.findReferenceTarget(myOwner.getManager(), qualifiedName, myOwner) != null) {
if (inferred) buffer.append("<i>");
PsiClassType type = JavaPsiFacade.getElementFactory(myOwner.getProject()).createType(myTargetClass, PsiSubstitutor.EMPTY);
buffer.append("@");
if (inferred && !generateLink) {
buffer.append(type.getPresentableText());
}
else {
JavaDocInfoGenerator.generateType(buffer, type, myOwner, generateLink, useShortNames && !external);
}
generateAnnotationAttributes(buffer, generateLink);
if (inferred) buffer.append("</i>");
}
else if (external || myResolveNotPossible) {
if (inferred) buffer.append("<i>");
String annoText = inferred ? "@" + myNameReference.getReferenceName() + myAnnotation.getParameterList().getText()
: myAnnotation.getText();
buffer.append(XmlStringUtil.escapeString(annoText));
if (inferred) buffer.append("</i>");
}
else {
buffer.append("<font color=red>");
buffer.append(XmlStringUtil.escapeString(myAnnotation.getText()));
buffer.append("</font>");
PsiClassType type =
myTargetClass != null && qualifiedName != null && JavaDocUtil.findReferenceTarget(myOwner.getManager(), qualifiedName, myOwner) != null
? JavaPsiFacade.getElementFactory(myOwner.getProject()).createType(myTargetClass, PsiSubstitutor.EMPTY)
: null;
boolean red = type == null && !myResolveNotPossible && !isInferred() && !isExternal();
if (isInferred()) buffer.append("<i>");
if (red) buffer.append("<font color=red>");
boolean generateLink = format != AnnotationFormat.ToolTip;
boolean forceShortNames = format != AnnotationFormat.JavaDocComplete;
buffer.append("@");
String name = forceShortNames ? myNameReference.getReferenceName() : myNameReference.getText();
if (type != null && generateLink) {
JavaDocInfoGenerator.generateLink(buffer, myTargetClass, name, false);
} else {
buffer.append(name);
}
if (red) buffer.append("</font>");
generateAnnotationAttributes(buffer, generateLink);
if (isInferred()) buffer.append("</i>");
}
private void generateAnnotationAttributes(StringBuilder buffer, boolean generateLink) {
@@ -189,3 +192,7 @@ class AnnotationDocGenerator {
return infos;
}
}
enum AnnotationFormat {
ToolTip, JavaDocShort, JavaDocComplete
}
@@ -400,13 +400,13 @@ public class JavaDocInfoGenerator {
public static String generateSignature(PsiElement element) {
StringBuilder buf = new StringBuilder();
if (element instanceof PsiClass) {
if (generateClassSignature(buf, (PsiClass)element, false)) return null;
if (generateClassSignature(buf, (PsiClass)element, SignaturePlace.ToolTip)) return null;
}
else if (element instanceof PsiField) {
generateFieldSignature(buf, (PsiField)element, false);
generateFieldSignature(buf, (PsiField)element, SignaturePlace.ToolTip);
}
else if (element instanceof PsiMethod) {
generateMethodSignature(buf, (PsiMethod)element, false, true);
generateMethodSignature(buf, (PsiMethod)element, SignaturePlace.ToolTip);
}
return buf.toString();
}
@@ -477,9 +477,11 @@ public class JavaDocInfoGenerator {
}
buffer.append("<PRE>");
if (generateClassSignature(buffer, aClass, true)) return;
if (generateClassSignature(buffer, aClass, SignaturePlace.Javadoc)) return;
buffer.append("</PRE>");
new NonCodeAnnotationGenerator(aClass, buffer).explainAnnotations();
PsiDocComment comment = getDocComment(aClass);
if (comment != null) {
generateCommonSection(buffer, comment);
@@ -489,8 +491,9 @@ public class JavaDocInfoGenerator {
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private static boolean generateClassSignature(StringBuilder buffer, PsiClass aClass, boolean generateLink) {
generateAnnotations(buffer, aClass, generateLink, true, false);
private static boolean generateClassSignature(StringBuilder buffer, PsiClass aClass, SignaturePlace place) {
boolean generateLink = place == SignaturePlace.Javadoc;
generateAnnotations(buffer, aClass, place, true);
String modifiers = PsiFormatUtil.formatModifiers(aClass, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
@@ -628,9 +631,11 @@ public class JavaDocInfoGenerator {
generateLinkToParentIfNeeded(buffer, field);
buffer.append("<PRE>");
generateFieldSignature(buffer, field, true);
generateFieldSignature(buffer, field, SignaturePlace.Javadoc);
buffer.append("</PRE>");
new NonCodeAnnotationGenerator(field, buffer).explainAnnotations();
ColorUtil.appendColorPreview(field, buffer);
PsiDocComment comment = getDocComment(field);
@@ -641,8 +646,10 @@ public class JavaDocInfoGenerator {
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private static void generateFieldSignature(StringBuilder buffer, PsiField field, boolean generateLink) {
generateAnnotations(buffer, field, generateLink, true, false);
private static void generateFieldSignature(StringBuilder buffer, PsiField field, SignaturePlace place) {
boolean generateLink = place == SignaturePlace.Javadoc;
generateAnnotations(buffer, field, place, true);
String modifiers = PsiFormatUtil.formatModifiers(field, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
@@ -876,13 +883,13 @@ public class JavaDocInfoGenerator {
}
}
private static void generateAnnotations(@NotNull StringBuilder buffer,
@NotNull PsiModifierListOwner owner,
boolean generateLink,
boolean splitAnnotations,
boolean useShortNames) {
private static void generateAnnotations(StringBuilder buffer,
PsiModifierListOwner owner,
SignaturePlace place,
boolean splitAnnotations) {
AnnotationFormat format = place == SignaturePlace.Javadoc ? AnnotationFormat.JavaDocShort : AnnotationFormat.ToolTip;
for (AnnotationDocGenerator anno : AnnotationDocGenerator.getAnnotationsToShow(owner)) {
anno.generateAnnotation(buffer, generateLink, useShortNames);
anno.generateAnnotation(buffer, format);
buffer.append("&nbsp;");
if (splitAnnotations) buffer.append("\n");
@@ -908,7 +915,7 @@ public class JavaDocInfoGenerator {
buffer.append(modifiers);
buffer.append(" ");
}
generateAnnotations(buffer, parameter, true, true, false);
generateAnnotations(buffer, parameter, SignaturePlace.Javadoc, true);
generateType(buffer, parameter.getType(), parameter);
buffer.append(" ");
buffer.append("<b>");
@@ -917,6 +924,8 @@ public class JavaDocInfoGenerator {
buffer.append("</b>");
buffer.append("</PRE>");
new NonCodeAnnotationGenerator(parameter, buffer).explainAnnotations();
final PsiElement method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class, PsiLambdaExpression.class);
if (method instanceof PsiMethod) {
@@ -943,9 +952,11 @@ public class JavaDocInfoGenerator {
generateLinkToParentIfNeeded(buffer, method);
buffer.append("<PRE>");
generateMethodSignature(buffer, method, true, false);
generateMethodSignature(buffer, method, SignaturePlace.Javadoc);
buffer.append("</PRE>");
new NonCodeAnnotationGenerator(method, buffer).explainAnnotations();
PsiDocComment comment = getMethodDocComment(method);
generateMethodDescription(buffer, method, comment);
@@ -983,8 +994,10 @@ public class JavaDocInfoGenerator {
}
}
private static void generateMethodSignature(StringBuilder buffer, PsiMethod method, boolean generateLink, boolean useShortNames) {
generateAnnotations(buffer, method, generateLink, true, useShortNames);
private static void generateMethodSignature(StringBuilder buffer, PsiMethod method, SignaturePlace place) {
boolean useShortNames = place == SignaturePlace.ToolTip;
boolean generateLink = place == SignaturePlace.Javadoc;
generateAnnotations(buffer, method, place, true);
String modifiers = PsiFormatUtil.formatModifiers(method, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
int indent = 0;
if (!modifiers.isEmpty()) {
@@ -1017,7 +1030,7 @@ public class JavaDocInfoGenerator {
PsiParameter[] parms = method.getParameterList().getParameters();
for (int i = 0; i < parms.length; i++) {
PsiParameter parm = parms[i];
generateAnnotations(buffer, parm, generateLink, false, useShortNames);
generateAnnotations(buffer, parm, place, false);
generateType(buffer, parm.getType(), method, generateLink, useShortNames);
buffer.append("&nbsp;");
if (parm.getName() != null) {
@@ -1744,7 +1757,7 @@ public class JavaDocInfoGenerator {
}
}
private static void generateLink(StringBuilder buffer, PsiElement element, String label, boolean plainLink) {
static void generateLink(StringBuilder buffer, PsiElement element, String label, boolean plainLink) {
String refText = JavaDocUtil.getReferenceText(element.getProject(), element);
if (refText != null) {
DocumentationManagerUtil.createHyperlink(buffer, element, refText, label, plainLink);
@@ -2180,4 +2193,8 @@ public class JavaDocInfoGenerator {
myBuffer.append(StringUtil.escapeXml(expression.getText()));
}
}
private enum SignaturePlace {
Javadoc, ToolTip
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.javadoc;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiParameter;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
class NonCodeAnnotationGenerator {
private final List<PsiModifierListOwner> myAllOwners = new ArrayList<>();
private final StringBuilder myOutput;
NonCodeAnnotationGenerator(PsiModifierListOwner owner, StringBuilder output) {
myOutput = output;
myAllOwners.add(owner);
if (owner instanceof PsiMethod) {
Collections.addAll(myAllOwners, ((PsiMethod)owner).getParameterList().getParameters());
}
}
void explainAnnotations() {
MultiMap<PsiModifierListOwner, AnnotationDocGenerator> generators = getNonCodeAnnotations();
if (generators.isEmpty()) return;
boolean hasExternal = generators.values().stream().anyMatch(AnnotationDocGenerator::isExternal);
boolean hasInferred = generators.values().stream().anyMatch(AnnotationDocGenerator::isInferred);
boolean hasBothKinds = hasExternal && hasInferred;
myOutput.append("\n");
myOutput.append(
hasBothKinds ? "External and <i>inferred</i>" : hasExternal ? "External" : "<i>Inferred</i>").append(" annotations available:<br>\n");
myOutput.append("<ul>\n");
generators.keySet().forEach(owner -> {
myOutput.append("<li>");
if (generators.size() > 1) {
myOutput.append(getKind(owner)).append(" <code>").append(((PsiNamedElement)owner).getName()).append("</code>: ");
}
List<AnnotationDocGenerator> annotations = ContainerUtil.newArrayList(generators.get(owner));
for (int i = 0; i < annotations.size(); i++) {
if (i > 0) myOutput.append(" ");
annotations.get(i).generateAnnotation(myOutput, AnnotationFormat.JavaDocComplete);
}
myOutput.append("</li>\n");
});
myOutput.append("</ul>\n");
}
@NotNull
private MultiMap<PsiModifierListOwner, AnnotationDocGenerator> getNonCodeAnnotations() {
MultiMap<PsiModifierListOwner, AnnotationDocGenerator> generators = MultiMap.createLinked();
for (PsiModifierListOwner owner : myAllOwners) {
List<AnnotationDocGenerator> nonCode =
ContainerUtil.filter(AnnotationDocGenerator.getAnnotationsToShow(owner), a -> a.isExternal() || a.isInferred());
if (!nonCode.isEmpty()) {
generators.putValues(owner, nonCode);
}
}
return generators;
}
private static String getKind(PsiModifierListOwner owner) {
if (owner instanceof PsiParameter) return "Parameter";
if (owner instanceof PsiMethod) {
return ((PsiMethod)owner).isConstructor() ? "Constructor" : "Method";
}
return owner.getClass().getName(); // unexpected
}
}
@@ -1,5 +1,10 @@
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://java.util.List"><code>java.util.List</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i>&nbsp;
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://java.util.List"><code>java.util.List</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>Contract</code></a>(pure = true)</i>&nbsp;
public abstract&nbsp;boolean&nbsp;<b>contains</b>(<a href="psi_element://java.lang.Object"><code>Object</code></a>&nbsp;o)</PRE>
<i>Inferred</i> annotations available:<br>
<ul>
<li><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i></li>
</ul>
Returns <tt>true</tt> if this list contains the specified element.
More formally, returns <tt>true</tt> if and only if this list contains
@@ -1,5 +1,10 @@
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://My"><code>My</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i>&nbsp;
boolean&nbsp;<b>contains</b>(<a href="psi_element://java.lang.Object"><code>Object</code></a>&nbsp;o)</PRE><DD><DL><DT><b>Description copied from interface:</b>&nbsp;<a href="psi_element://java.util.Collection"><code>java.util.Collection</code></a><br>
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://My"><code>My</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>Contract</code></a>(pure = true)</i>&nbsp;
boolean&nbsp;<b>contains</b>(<a href="psi_element://java.lang.Object"><code>Object</code></a>&nbsp;o)</PRE>
<i>Inferred</i> annotations available:<br>
<ul>
<li><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i></li>
</ul>
<DD><DL><DT><b>Description copied from interface:</b>&nbsp;<a href="psi_element://java.util.Collection"><code>java.util.Collection</code></a><br>
Returns <tt>true</tt> if this collection contains the specified
element. More formally, returns <tt>true</tt> if and only if this
collection contains at least one element <tt>e</tt> such that
@@ -1,4 +1,9 @@
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://En"><code>En</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i>&nbsp;
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://En"><code>En</code></a></b></small><PRE><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>Contract</code></a>(pure = true)</i>&nbsp;
static&nbsp;int&nbsp;<b>valueOf</b>(int&nbsp;i)</PRE>
<i>Inferred</i> annotations available:<br>
<ul>
<li><i>@<a href="psi_element://org.jetbrains.annotations.Contract"><code>org.jetbrains.annotations.Contract</code></a>(pure = true)</i></li>
</ul>
myjavadoc
<DD><DL><DT><b>Parameters:</b><DD><code>i</code> - </DD></DL></DD><DD><DL><DT><b>Returns:</b><DD></DD></DL></DD></body></html>
@@ -156,8 +156,8 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
setUpExternalUpAnnotations();
openDecompiledClass("java.lang.Boolean");
checkHasGutter("<html>External and <i>inferred</i> annotations available. Full signature:<p>\n" +
"@org.jetbrains.annotations.Contract(&quot;null-&gt;false&quot;)&nbsp;\n" +
"private static&nbsp;boolean&nbsp;<b>toBoolean</b>(@org.jetbrains.annotations.Nullable&nbsp;String&nbsp;var0)</html>");
"@Contract(&quot;null-&gt;false&quot;)&nbsp;\n" +
"private static&nbsp;boolean&nbsp;<b>toBoolean</b>(@Nullable&nbsp;String&nbsp;var0)</html>");
}
private void checkHasGutter(String expectedText) {