From 1ff1f26e15fed1cc40d054492462e486ca21346a Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 2 Apr 2024 15:42:31 +0200 Subject: [PATCH] [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 --- .../javadoc/AnnotationDocGenerator.java | 25 +++++++++++++--- .../javadoc/JavaDocInfoGenerator.java | 29 ++++++++++++------- .../javadocIG/inferredAnnotationsOnArray.html | 1 + .../javadocIG/inferredAnnotationsOnArray.java | 7 +++++ .../inferredAnnotationsOnArray2d.html | 1 + .../inferredAnnotationsOnArray2d.java | 9 ++++++ .../inferredAnnotationsOnArrayMethod.html | 1 + .../inferredAnnotationsOnArrayMethod.java | 5 ++++ .../javadocIG/reflectConstructor.html | 3 +- .../javadocIG/typeAnnoMultiDimArray.html | 1 + .../javadocIG/typeAnnoMultiDimArray.java | 13 +++++++++ .../javadoc/JavaDocInfoGeneratorTest.java | 12 ++++++-- 12 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.html create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.java create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.html create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.java create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.html create mode 100644 java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.java create mode 100644 java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.html create mode 100644 java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.java diff --git a/java/java-impl/src/com/intellij/codeInsight/javadoc/AnnotationDocGenerator.java b/java/java-impl/src/com/intellij/codeInsight/javadoc/AnnotationDocGenerator.java index ae4ae264fb27..ab8cdd903f1b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/javadoc/AnnotationDocGenerator.java +++ b/java/java-impl/src/com/intellij/codeInsight/javadoc/AnnotationDocGenerator.java @@ -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 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 shownAnnotations = new HashSet<>(); - return ContainerUtil.mapNotNull(owner.getAnnotations(), - annotation -> forAnnotation(context, shownAnnotations, annotation)); + List 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 getAnnotationsToShow(@NotNull PsiModifierListOwner owner) { diff --git a/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocInfoGenerator.java b/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocInfoGenerator.java index 80022761a73e..8d1498a4b105 100644 --- a/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocInfoGenerator.java +++ b/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocInfoGenerator.java @@ -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); diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.html b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.html new file mode 100644 index 000000000000..c56769ffb8e6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.html @@ -0,0 +1 @@ +
void test(
String @NotNulli [] data
)

\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.java b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.java new file mode 100644 index 000000000000..2be2400141f3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray.java @@ -0,0 +1,7 @@ +class Test { + void test(String[] data) { + for (String item : data) { + System.out.println(item); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.html b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.html new file mode 100644 index 000000000000..9c0a1939617d --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.html @@ -0,0 +1 @@ +
void test2d(
String @NotNulli [][] data
)

\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.java b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.java new file mode 100644 index 000000000000..63e5f4088439 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArray2d.java @@ -0,0 +1,9 @@ +class Test { + void test2d(String[][] data) { + for (String[] row : data) { + for (String item : row) { + System.out.println(item); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.html b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.html new file mode 100644 index 000000000000..80dc145df475 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.html @@ -0,0 +1 @@ +
final String @Nullablei [] test()

\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.java b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.java new file mode 100644 index 000000000000..bcd0928d3f08 --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/inferredAnnotationsOnArrayMethod.java @@ -0,0 +1,5 @@ +class Test { + final String[] test() { + return Math.random() > 0.5 ? new String[] { "1", "2", "3" } : null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/reflectConstructor.html b/java/java-tests/testData/codeInsight/javadocIG/reflectConstructor.html index b9742f4b7551..d445e3dc528d 100644 --- a/java/java-tests/testData/codeInsight/javadocIG/reflectConstructor.html +++ b/java/java-tests/testData/codeInsight/javadocIG/reflectConstructor.html @@ -1,5 +1,4 @@
@CallerSensitive 
-@NotNull 
 @Contract(pure = true) 
-public java.lang.reflect.Constructor<?>[] getDeclaredConstructors()
+public java.lang.reflect.Constructor<?> @NotNull [] getDeclaredConstructors()
 throws SecurityException

Throws:

SecurityException

 < java 10 >
\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.html b/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.html new file mode 100644 index 000000000000..b2801dff3eaf --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.html @@ -0,0 +1 @@ +
void test(
@Test(1) String @Test(2) [] @Test(3) [] test
)

\ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.java b/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.java new file mode 100644 index 000000000000..72bc0f06078c --- /dev/null +++ b/java/java-tests/testData/codeInsight/javadocIG/typeAnnoMultiDimArray.java @@ -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(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java index c3ac19144939..088b0bf8bea1 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/javadoc/JavaDocInfoGeneratorTest.java @@ -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(); }