[java-doc] Fix rendering type-annotations on arrays (including non-source annotations)

Fixes IDEA-350503 Type annotations in JavaDoc are shown in wrong order for multi-dimensional array
Fixes IDEA-300381 Inferred @NotNull Annotation on Array / VarArg method parameter incorrectly annotates the array elements and not the argument.

GitOrigin-RevId: 4a5919df70c3bac9b19fb7019365636b9a56273a
This commit is contained in:
Tagir Valeev
2024-04-03 09:29:48 +00:00
committed by intellij-monorepo-bot
parent 66abe1b3c6
commit 1ff1f26e15
12 changed files with 87 additions and 20 deletions
@@ -79,6 +79,10 @@ public final class AnnotationDocGenerator {
public String getAnnotationQualifiedName() {
return myAnnotation.getQualifiedName();
}
boolean isNonCodeTypeUseAnnotation() {
return (isExternal() || isInferred()) && AnnotationTargetUtil.isTypeAnnotation(myAnnotation);
}
public boolean isInferred() {
return AnnotationUtil.isInferredAnnotation(myAnnotation);
@@ -291,12 +295,25 @@ public final class AnnotationDocGenerator {
}
public static List<AnnotationDocGenerator> getAnnotationsToShow(@NotNull PsiAnnotationOwner owner, @NotNull PsiElement context) {
if (owner instanceof PsiModifierList) {
return getAnnotationsToShow(((PsiModifierListOwner)((PsiModifierList)owner).getParent()));
if (owner instanceof PsiModifierList modifierList) {
return getAnnotationsToShow(((PsiModifierListOwner)modifierList.getParent()));
}
Set<String> shownAnnotations = new HashSet<>();
return ContainerUtil.mapNotNull(owner.getAnnotations(),
annotation -> forAnnotation(context, shownAnnotations, annotation));
List<AnnotationDocGenerator> generators = ContainerUtil.mapNotNull(
owner.getAnnotations(), annotation -> forAnnotation(context, shownAnnotations, annotation));
if (owner instanceof PsiArrayType type) {
PsiType contextType = getContextType(context);
if (type.equals(contextType)) {
return StreamEx.of(getAnnotationsToShow((PsiModifierListOwner)context)).filter(anno -> anno.isNonCodeTypeUseAnnotation())
.append(generators).toList();
}
}
return generators;
}
static @Nullable PsiType getContextType(@NotNull PsiElement context) {
return context instanceof PsiVariable var ? var.getType() :
context instanceof PsiMethod method ? method.getReturnType() : null;
}
public static List<AnnotationDocGenerator> getAnnotationsToShow(@NotNull PsiModifierListOwner owner) {
@@ -1384,6 +1384,7 @@ public class JavaDocInfoGenerator {
AnnotationFormat format = place == SignaturePlace.Javadoc ? AnnotationFormat.JavaDocShort : AnnotationFormat.ToolTip;
for (AnnotationDocGenerator anno : AnnotationDocGenerator.getAnnotationsToShow(owner)) {
if (ignoreNonSourceAnnotations && (anno.isInferred() || anno.isExternal())) continue;
if (anno.isNonCodeTypeUseAnnotation() && AnnotationDocGenerator.getContextType(owner) instanceof PsiArrayType) continue;
anno.generateAnnotation(buffer, format, generateLink, isRendered(), doHighlightSignatures());
buffer.append(NBSP);
@@ -1621,7 +1622,7 @@ public class JavaDocInfoGenerator {
buffer.append(StringUtil.repeatSymbol(' ', indent));
PsiParameter parm = parameters[i];
generateAnnotations(buffer, parm, place, false, false, true);
generateType(buffer, parm.getType(), method, generateLink, isTooltip);
generateType(buffer, parm.getType(), parm, generateLink, isTooltip);
if (!isTooltip) {
buffer.append(NBSP);
appendStyledSpan(buffer, getHighlightingManager().getParameterAttributes(), parm.getName());
@@ -2898,18 +2899,24 @@ public class JavaDocInfoGenerator {
* @return Length of the generated label.
*/
public int generateType(StringBuilder buffer, PsiType type, PsiElement context, boolean generateLink, boolean useShortNames) {
if (type instanceof PsiArrayType) {
int rest = generateType(buffer, ((PsiArrayType)type).getComponentType(), context, generateLink, useShortNames);
if (type instanceof PsiArrayType arrayType) {
int len = generateType(buffer, arrayType.getDeepComponentType(), context, generateLink, useShortNames);
int len = generateTypeAnnotations(buffer, type, context, generateLink, true);
if (type instanceof PsiEllipsisType) {
buffer.append("...");
return len + rest + 3;
}
else {
appendStyledSpan(buffer, getHighlightingManager().getBracketsAttributes(), "[]");
return len + rest + 2;
int dimensions = arrayType.getArrayDimensions();
PsiType curType = arrayType;
for (int i = 0; i < dimensions; i++) {
len += generateTypeAnnotations(buffer, curType, context, generateLink, true);
if (i == dimensions - 1 && type instanceof PsiEllipsisType) {
buffer.append("...");
len += 3;
}
else {
appendStyledSpan(buffer, getHighlightingManager().getBracketsAttributes(), "[]");
len += 2;
}
curType = ((PsiArrayType)curType).getComponentType();
}
return len;
}
int typAnnoLength = generateTypeAnnotations(buffer, type, context, generateLink, false);
@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://Test"><code><span style="color:#000000;">Test</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">void</span>&nbsp;<span style="color:#000000;">test</span><span style="">(</span><br> <a href="psi_element://java.lang.String"><code><span style="color:#000000;">String</span></code></a>&nbsp;<i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.NotNull"><code><span style="color:#808000;">NotNull</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></a>&nbsp;<span style="">[]</span>&nbsp;<span style="color:#000000;">data</span><br><span style="">)</span></pre></div><table class='sections'><p></table>
@@ -0,0 +1,7 @@
class Test {
void test(String[] data) {
for (String item : data) {
System.out.println(item);
}
}
}
@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://Test"><code><span style="color:#000000;">Test</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">void</span>&nbsp;<span style="color:#000000;">test2d</span><span style="">(</span><br> <a href="psi_element://java.lang.String"><code><span style="color:#000000;">String</span></code></a>&nbsp;<i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.NotNull"><code><span style="color:#808000;">NotNull</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></a>&nbsp;<span style="">[]</span><span style="">[]</span>&nbsp;<span style="color:#000000;">data</span><br><span style="">)</span></pre></div><table class='sections'><p></table>
@@ -0,0 +1,9 @@
class Test {
void test2d(String[][] data) {
for (String[] row : data) {
for (String item : row) {
System.out.println(item);
}
}
}
}
@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://Test"><code><span style="color:#000000;">Test</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">final</span>&nbsp;<a href="psi_element://java.lang.String"><code><span style="color:#000000;">String</span></code></a>&nbsp;<i><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.Nullable"><code><span style="color:#808000;">Nullable</span></code></a></i><sup><font color="808080" size="3"><i>i</i></font></sup><a href="inferred.annotations"><icon src="AllIcons.Ide.External_link_arrow"></a>&nbsp;<span style="">[]</span>&nbsp;<span style="color:#000000;">test</span><span style="">(</span><span style="">)</span></pre></div><table class='sections'><p></table>
@@ -0,0 +1,5 @@
class Test {
final String[] test() {
return Math.random() > 0.5 ? new String[] { "1", "2", "3" } : null;
}
}
@@ -1,5 +1,4 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class" />&nbsp;<a href="psi_element://java.lang.Class"><code><span style="color:#000000;">java.<wbr>lang.<wbr>Class</span><span style="">&lt;</span><span style="color:#20999d;">T</span><span style="">&gt;</span></code></a></div><div class="definition"><pre><span style="color:#ff0000">@<span style="color:#808000;">CallerSensitive</span></span>&nbsp;
<span style="color:#808000;">@</span><span style="color:#808000;">NotNull</span><a href="external.annotations"><icon src="AllIcons.Ide.External_link_arrow" /></a>&nbsp;
<span style="color:#808000;">@</span><span style="color:#808000;">Contract</span><span style="">(</span><span style="">pure</span><span style=""> = </span><span style="color:#000080;font-weight:bold;">true</span><span style="">)</span><a href="external.annotations"><icon src="AllIcons.Ide.External_link_arrow" /></a>&nbsp;
<span style="color:#000080;font-weight:bold;">public</span>&nbsp;<a href="psi_element://java.lang.reflect.Constructor"><code><span style="color:#000000;">java.<wbr>lang.<wbr>reflect.<wbr>Constructor</span></code></a><span style="">&lt;</span><span style="">?</span><span style="">&gt;</span><span style="">[]</span>&nbsp;<span style="color:#000000;">getDeclaredConstructors</span><span style="">(</span><span style="">)</span>
<span style="color:#000080;font-weight:bold;">public</span>&nbsp;<a href="psi_element://java.lang.reflect.Constructor"><code><span style="color:#000000;">java.<wbr>lang.<wbr>reflect.<wbr>Constructor</span></code></a><span style="">&lt;</span><span style="">?</span><span style="">&gt;</span>&nbsp;<span style="color:#808000;">@</span><span style="color:#808000;">NotNull</span><a href="external.annotations"><icon src="AllIcons.Ide.External_link_arrow" /></a>&nbsp;<span style="">[]</span>&nbsp;<span style="color:#000000;">getDeclaredConstructors</span><span style="">(</span><span style="">)</span>
<span style="color:#000080;font-weight:bold;">throws</span>&nbsp;<a href="psi_element://java.lang.SecurityException"><code><span style="color:#000000;">SecurityException</span></code></a></pre></div><table class="sections"><tbody><tr><td valign="top" class="section"><p>Throws:</p></td><td valign="top"><p><a href="psi_element://java.lang.SecurityException"><code><span style="color:#0000ff;">SecurityException</span></code></a></p></td></tr></tbody></table><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder" />&nbsp;&lt; java 10 &gt;</div></body></html>
@@ -0,0 +1 @@
<html><head><base href="placeholder"></head><body><div class="bottom"><icon src="AllIcons.Nodes.Class">&nbsp;<a href="psi_element://Test"><code><span style="color:#000000;">Test</span></code></a></div><div class='definition'><pre><span style="color:#000080;font-weight:bold;">void</span>&nbsp;<span style="color:#000000;">test</span><span style="">(</span><br> <span style="color:#808000;">@</span><a href="psi_element://Test.Test"><code><span style="color:#808000;">Test</span></code></a><span style="">(</span><span style="color:#0000ff;">1</span><span style="">)</span>&nbsp;<a href="psi_element://java.lang.String"><code><span style="color:#000000;">String</span></code></a>&nbsp;<span style="color:#808000;">@</span><a href="psi_element://Test.Test"><code><span style="color:#808000;">Test</span></code></a><span style="">(</span><span style="color:#0000ff;">2</span><span style="">)</span>&nbsp;<span style="">[]</span>&nbsp;<span style="color:#808000;">@</span><a href="psi_element://Test.Test"><code><span style="color:#808000;">Test</span></code></a><span style="">(</span><span style="color:#0000ff;">3</span><span style="">)</span>&nbsp;<span style="">[]</span>&nbsp;<span style="color:#000000;">test</span><br><span style="">)</span></pre></div><table class='sections'><p></table>
@@ -0,0 +1,13 @@
import java.lang.annotation.*;
class Test {
void test(@Test(1) String @Test(2) [] @Test(3) [] test) {
}
@Target(ElementType.TYPE_USE)
@Documented
@interface Test {
int value();
}
}
@@ -4,6 +4,7 @@ package com.intellij.java.codeInsight.javadoc;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.JavaCodeInsightTestCase;
import com.intellij.codeInsight.daemon.impl.quickfix.JetBrainsAnnotationsExternalLibraryResolver;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
import com.intellij.java.codeInsight.JavaExternalDocumentationTest;
import com.intellij.lang.java.JavaDocumentationProvider;
@@ -20,14 +21,14 @@ import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.testFramework.core.FileComparisonFailedError;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.platform.testFramework.core.FileComparisonFailedError;
import com.intellij.testFramework.DumbModeTestUtils;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
import com.intellij.testFramework.fixtures.MavenDependencyUtil;
import com.intellij.util.lang.JavaVersion;
import com.intellij.util.ui.UIUtil;
import org.intellij.lang.annotations.Flow;
@@ -59,7 +60,8 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
super.setUpModule();
if (!getTestName(false).equals("HideNonDocumentedFlowAnnotations")) {
ModuleRootModificationUtil.updateModel(
myModule, model -> DefaultLightProjectDescriptor.addJetBrainsAnnotations(model));
myModule, model -> MavenDependencyUtil.addFromMaven(
model, "org.jetbrains:annotations:" + JetBrainsAnnotationsExternalLibraryResolver.getVersion()));
}
}
@@ -90,6 +92,10 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
public void testInitializerWithReference() { doTestField(); }
public void testAnnotations() { doTestField(); }
public void testAnnotationsInParams() { doTestMethod(); }
public void testInferredAnnotationsOnArray() { doTestMethod(); }
public void testInferredAnnotationsOnArrayMethod() { doTestMethod(); }
public void testInferredAnnotationsOnArray2d() { doTestMethod(); }
public void testTypeAnnoMultiDimArray() { doTestMethod(); }
public void testApiNotes() { doTestMethod(); }
public void testLiteral() { doTestField(); }
public void testEscapingInLiteral() { doTestField(); }