[Java. Code Formatting] Rewrite the way of detection type annotation in JavaFormatterUtil

IDEA-353192

GitOrigin-RevId: 34e375e4e1e059be03aad12c2839911b8315ed06
This commit is contained in:
Georgii Ustinov
2024-06-04 06:46:24 +00:00
committed by intellij-monorepo-bot
parent c7a93d5462
commit a4d5e41d7f
23 changed files with 336 additions and 51 deletions
@@ -19,15 +19,13 @@ import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
public final class JavaFormatterUtil {
/**
@@ -38,6 +36,11 @@ public final class JavaFormatterUtil {
private static final int CALL_EXPRESSION_DEPTH = 500;
private static final Set<String> KNOWN_TYPE_ANNOTATIONS = Set.of(
"org.jetbrains.annotations.NotNull",
"org.jetbrains.annotations.Nullable"
);
private JavaFormatterUtil() { }
/**
@@ -289,7 +292,8 @@ public final class JavaFormatterUtil {
if (prev != null && prev.getElementType() == JavaElementType.MODIFIER_LIST) {
ASTNode last = prev.getLastChildNode();
if (last != null && last.getElementType() == JavaElementType.ANNOTATION) {
if (javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION && isModifierListWithSingleAnnotation(prev, JavaElementType.FIELD) ||
if (isTypeAnnotation(last) ||
javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION && isModifierListWithSingleAnnotation(prev, JavaElementType.FIELD) ||
javaSettings.DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION_IN_PARAMETER &&
isModifierListWithSingleAnnotation(prev, JavaElementType.PARAMETER) ||
isAnnotationAfterKeyword(last)
@@ -311,10 +315,17 @@ public final class JavaFormatterUtil {
if (prev instanceof PsiKeyword) {
return null;
}
else if (isAnnoInsideModifierListWithAtLeastOneKeyword(child, parent)) {
return Wrap.createWrap(WrapType.NONE, false);
}
if (isTypeAnnotation(child)) {
if (prev == null || prev.getElementType() != JavaElementType.ANNOTATION || isTypeAnnotation(prev)) {
return Wrap.createWrap(WrapType.NONE, false);
}
}
return Wrap.createWrap(getWrapType(getAnnotationWrapType(parent.getTreeParent(), child, settings, javaSettings)), true);
}
else if (childType == JavaTokenType.END_OF_LINE_COMMENT) {
@@ -432,6 +443,33 @@ public final class JavaFormatterUtil {
return prev != null && prev.getElementType() != JavaElementType.BLOCK_STATEMENT;
}
private static boolean isTypeAnnotation(@NotNull ASTNode child) {
PsiElement node = child.getPsi();
PsiElement next = PsiTreeUtil.skipSiblingsForward(node, PsiWhiteSpace.class, PsiAnnotation.class);
if (next instanceof PsiKeyword) return false;
if (!(node instanceof PsiAnnotation psiAnnotation)) return false;
PsiJavaCodeReferenceElement psiReference = psiAnnotation.getNameReferenceElement();
if (psiReference == null) return false;
if (psiReference.isQualified()) {
return KNOWN_TYPE_ANNOTATIONS.contains(psiReference.getText());
}
else {
PsiElement referenceName = psiReference.getReferenceNameElement();
if (referenceName == null) return false;
if (!(psiReference.getContainingFile() instanceof PsiJavaFile javaFile)) return false;
PsiImportList importList = javaFile.getImportList();
if (importList == null) return false;
String referenceNameText = referenceName.getText();
return ContainerUtil.or(KNOWN_TYPE_ANNOTATIONS, fqn -> {
if (!fqn.endsWith(referenceNameText)) return false;
String packageName = StringUtil.getPackageName(fqn);
return importList.findOnDemandImportStatement(packageName) != null || importList.findSingleClassImportStatement(fqn) != null;
});
}
}
private static void putPreferredWrapInParentBlock(@NotNull AbstractJavaBlock block, @NotNull Wrap preferredWrap) {
AbstractJavaBlock parentBlock = block.getParentBlock();
if (parentBlock != null) {
@@ -13,8 +13,7 @@ class Y extends X{
}
}
class Z extends Y {
@NotNull
String dontAnnotateBase<caret>() {// trigger quick fix for inspection here
@NotNull String dontAnnotateBase() {// trigger quick fix for inspection here
return "Z";
}
}
@@ -9,15 +9,13 @@ abstract class P2 {
}
class PPP extends P2 {
@NotNull
String foo(P p) {
@NotNull String foo(P p) {
return super.foo(p);
}
}
class PPP2 extends P2 {
@NotNull
String foo(P p) {
@NotNull String foo(P p) {
return super.foo(p);
}
}
@@ -9,8 +9,7 @@ import org.jetbrains.annotations.*;
}
}
class XC extends XEM {
@NotNull
String f() {
@NotNull String f() {
return "";
}
}
@@ -6,8 +6,7 @@ public interface MyTestClass {
}
public class MyRealTestClass implements MyTestClass {
@NotNull
String implementMe(String arg) {
@NotNull String implementMe(String arg) {
}
}
@@ -1,6 +1,5 @@
package withAnnotation;
import org.jetbrains.annotations.Nullable;
public class Foo {
@Nullable
String f<caret>oo() {return null;}
@Nullable String f<caret>oo() {return null;}
}
@@ -39,8 +39,7 @@ class Test {
final String myFoo13 = null;
final Runnable myFoo14 = new Runnable() {
{foo();}
@Nullable
Object foo() {
@Nullable Object foo() {
return null;
}
public void run() {}
@@ -59,8 +58,7 @@ class Test {
myFoo12 = "";
}
@Nullable
String foo10(boolean flag) {
@Nullable String foo10(boolean flag) {
return flag ? foo2() : foo3();
}
}
@@ -1,13 +1,11 @@
import org.jetbrains.annotations.*;
class Test {
@Nullable
String foo1() {
@Nullable String foo1() {
return null;
}
@NotNull
String foo2() {
@NotNull String foo2() {
return "";
}
@@ -15,29 +13,24 @@ class Test {
return s;
}
@NotNull
String foo4(@NotNull String s) {
@NotNull String foo4(@NotNull String s) {
return s.substring(0);
}
@NotNull
Integer foo5(Integer i) {
@NotNull Integer foo5(Integer i) {
return i++;
}
@NotNull
Integer foo6(Integer i) {
@NotNull Integer foo6(Integer i) {
if (i == 0) return 1;
return i * foo6(i--);
}
@Nullable
Integer foo7(boolean flag) {
@Nullable Integer foo7(boolean flag) {
return flag ? null : 1;
}
@Nullable
Integer foo8(boolean flag) {
@Nullable Integer foo8(boolean flag) {
if (flag) {
return null;
}
@@ -51,8 +44,7 @@ class Test {
return foo3("");
}
@Nullable
String foo9() {
@Nullable String foo9() {
return bar9();
}
@@ -67,16 +59,13 @@ class Test {
return foo3("");
}
@Nullable
String foo10(boolean flag) {
@Nullable String foo10(boolean flag) {
return flag ? bar10() : bar101();
}
@NotNull
String foo11() {
@NotNull String foo11() {
class Foo{
@Nullable
String mess() {
@Nullable String mess() {
return null;
}
}
@@ -11,20 +11,17 @@ class Test {
return str;
}
@NotNull
String foo1(@Nullable String str) {
@NotNull String foo1(@Nullable String str) {
if (str == null) return "null";
return (str);
}
@NotNull
String foo2(@Nullable String str) {
@NotNull String foo2(@Nullable String str) {
if (str == null) return "null";
return ((String)str);
}
@NotNull
String fram(@Nullable String str, boolean b) {
@NotNull String fram(@Nullable String str, boolean b) {
if (str != null) {
return b ? str : "not null strimg";
}
@@ -0,0 +1,18 @@
package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.List;
@Target({ElementType.TYPE_USE})
@interface CustomAnno {}
public class Formatter {
@CustomAnno String getCustomString() {
return null;
}
@CustomAnno <T, V> List<T> getCustomList() {
return null;
}
}
@@ -0,0 +1,21 @@
package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.List;
@Target({ElementType.TYPE_USE})
@interface CustomAnno {
}
public class Formatter {
@CustomAnno
String getCustomString() {
return null;
}
@CustomAnno
<T, V> List<T> getCustomList() {
return null;
}
}
@@ -0,0 +1,14 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Formatter {
@NotNull String getNotNullName() {
return "";
}
@Nullable String getNullableName() {
return null;
}
}
@@ -0,0 +1,16 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull <T, V> List<T> getNotNullList() {
return List.of();
}
@Nullable <T, V> List<T> getNullableList() {
return null;
}
}
@@ -0,0 +1,16 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull <T, V> List<T> getNotNullList() {
return List.of();
}
@Nullable <T, V> List<T> getNullableList() {
return null;
}
}
@@ -0,0 +1,14 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Formatter {
@NotNull String getNotNullName() {
return "";
}
@Nullable String getNullableName() {
return null;
}
}
@@ -0,0 +1,17 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@Nullable @NotNull String getCustomString() {
return null;
}
@Nullable @NotNull <T, V> List<T> getCustomList() {
return null;
}
}
@@ -0,0 +1,17 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@Nullable @NotNull String getCustomString() {
return null;
}
@Nullable @NotNull <T, V> List<T> getCustomList() {
return null;
}
}
@@ -0,0 +1,20 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull
@Nullable
<T, V> List<T> getStrangeList() {
return List.of();
}
@NotNull
@Nullable
String getNotNullName() {
return "";
}
}
@@ -0,0 +1,19 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull
@Nullable <T, V> List<T> getStrangeList() {
return List.of();
}
@NotNull
@Nullable
String getNotNullName() {
return "";
}
}
@@ -0,0 +1,28 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull
<T, V> List<T> getNotNullList() {
return List.of();
}
@Nullable
<T, V> List<T> getNullableList() {
return null;
}
@NotNull
String getNotNullName() {
return "";
}
@Nullable
String getNullableName() {
return null;
}
}
@@ -0,0 +1,26 @@
package org.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Formatter {
@NotNull <T, V> List<T> getNotNullList() {
return List.of();
}
@Nullable <T, V> List<T> getNullableList() {
return null;
}
@NotNull
String getNotNullName() {
return "";
}
@Nullable
String getNullableName() {
return null;
}
}
@@ -0,0 +1,44 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.psi.formatter.java
import com.intellij.JavaTestUtil
import com.intellij.application.options.CodeStyle
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.CommonCodeStyleSettings
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.WRAP_ALWAYS
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
class TypeAnnotationFormatterTest : LightJavaCodeInsightFixtureTestCase() {
private val commonSettings: CommonCodeStyleSettings
get() = CodeStyle.getSettings(project).getCommonSettings(JavaLanguage.INSTANCE)
override fun getTestDataPath() = "${JavaTestUtil.getJavaTestDataPath()}/psi/formatter/java/typeAnnotation/"
override fun setUp() {
super.setUp()
commonSettings.METHOD_ANNOTATION_WRAP = WRAP_ALWAYS
ModuleRootModificationUtil.updateModel(module, DefaultLightProjectDescriptor::addJetBrainsAnnotations)
}
fun testKnownAnnotationBeforeType() = doTest()
fun testKnownAnnotationBeforeTypeParameterList() = doTest()
fun testCustomAnnotation() = doTest()
fun testManyKnownAnnotations() = doTest()
fun testPreserveWrappingSingleAnnotation() = doTest()
fun testPreserveWrappingManyAnnotations() = doTest()
private fun doTest() {
val testName = getTestName(false)
myFixture.configureByFile("$testName.java")
WriteCommandAction.runWriteCommandAction(project) { CodeStyleManager.getInstance(project).reformatText(file, 0, editor.document.textLength) }
myFixture.checkResultByFile("${testName}_after.java")
}
}
@@ -2,6 +2,5 @@ import org.jetbrains.annotations.Nullable;
interface A {
@Nullable
<T, U> U foo(U u, T t);
@Nullable <T, U> U foo(U u, T t);
}