AddAnnotationPsiFix: support annotating lambda parameter without a type

GitOrigin-RevId: 11fe3fdfa77f1d52c1e122dd790b3375488496f6
This commit is contained in:
Tagir Valeev
2020-02-13 06:33:23 +00:00
committed by intellij-monorepo-bot
parent 494af61fae
commit ca4cd28b82
12 changed files with 129 additions and 77 deletions
@@ -15,8 +15,11 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -66,13 +69,11 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
return CodeInsightBundle.message("inspection.i18n.quickfix.annotate.as", shortName);
}
@Nullable
public static PsiModifierListOwner getContainer(final PsiFile file, int offset) {
public static @Nullable PsiModifierListOwner getContainer(PsiFile file, int offset) {
return getContainer(file, offset, false);
}
@Nullable
public static PsiModifierListOwner getContainer(final PsiFile file, int offset, boolean availableOnReference) {
public static @Nullable PsiModifierListOwner getContainer(PsiFile file, int offset, boolean availableOnReference) {
PsiReference reference = availableOnReference ? file.findReferenceAt(offset) : null;
if (reference != null) {
PsiElement target = reference.resolve();
@@ -97,14 +98,12 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
}
@Override
@NotNull
public String getText() {
public @NotNull String getText() {
return myText;
}
@Override
@NotNull
public String getFamilyName() {
public @NotNull String getFamilyName() {
return CodeInsightBundle.message("intention.add.annotation.family");
}
@@ -174,8 +173,7 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
}
}
@NotNull
private ExternalAnnotationsManager.AnnotationPlace choosePlace(@NotNull PsiModifierListOwner modifierListOwner) {
private @NotNull ExternalAnnotationsManager.AnnotationPlace choosePlace(@NotNull PsiModifierListOwner modifierListOwner) {
Project project = modifierListOwner.getProject();
final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project);
PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(myAnnotation, modifierListOwner.getResolveScope());
@@ -213,6 +211,7 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
}
public static PsiAnnotation addPhysicalAnnotation(String fqn, PsiNameValuePair[] pairs, PsiAnnotationOwner owner) {
owner = expandParameterIfNecessary(owner);
PsiAnnotation inserted = owner.addAnnotation(fqn);
for (PsiNameValuePair pair : pairs) {
inserted.setDeclaredAttributeValue(pair.getName(), pair.getValue());
@@ -220,6 +219,36 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
return inserted;
}
private static PsiAnnotationOwner expandParameterIfNecessary(PsiAnnotationOwner owner) {
if (owner instanceof PsiModifierList) {
PsiParameter parameter = ObjectUtils.tryCast(((PsiModifierList)owner).getParent(), PsiParameter.class);
if (parameter != null && parameter.getTypeElement() == null) {
PsiParameterList list = ObjectUtils.tryCast(parameter.getParent(), PsiParameterList.class);
if (list != null && list.getParent() instanceof PsiLambdaExpression) {
PsiParameter[] parameters = list.getParameters();
int index = ArrayUtil.indexOf(parameters, parameter);
PsiParameterList newList;
if (PsiUtil.isLanguageLevel11OrHigher(list)) {
String newListText = StreamEx.of(parameters).map(p -> "var " + p.getName()).joining(",", "(", ")");
newList = ((PsiLambdaExpression)JavaPsiFacade.getElementFactory(list.getProject())
.createExpressionFromText(newListText+" -> {}", null)).getParameterList();
newList = (PsiParameterList)new CommentTracker().replaceAndRestoreComments(list, newList);
} else {
newList = LambdaUtil.specifyLambdaParameterTypes((PsiLambdaExpression)list.getParent());
}
if (newList != null) {
list = newList;
parameter = list.getParameter(index);
LOG.assertTrue(parameter != null);
owner = parameter.getModifierList();
LOG.assertTrue(owner != null);
}
}
}
}
return owner;
}
public static void removePhysicalAnnotations(@NotNull PsiModifierListOwner owner, String @NotNull ... fqns) {
for (String fqn : fqns) {
PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, true, fqn);
@@ -247,8 +276,7 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
* @param owner an element to add the annotation
* @return newly created fix or null if adding nullability annotation is impossible for the specified element.
*/
@Nullable
public static AddAnnotationPsiFix createAddNullableFix(PsiModifierListOwner owner) {
public static @Nullable AddAnnotationPsiFix createAddNullableFix(PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
return createAddNullableNotNullFix(owner, manager.getDefaultNullable(), manager.getNotNulls());
}
@@ -259,15 +287,13 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement {
* @param owner an element to add the annotation
* @return newly created fix or null if adding nullability annotation is impossible for the specified element.
*/
@Nullable
public static AddAnnotationPsiFix createAddNotNullFix(PsiModifierListOwner owner) {
public static @Nullable AddAnnotationPsiFix createAddNotNullFix(PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
return createAddNullableNotNullFix(owner, manager.getDefaultNotNull(), manager.getNullables());
}
@Nullable
private static AddAnnotationPsiFix createAddNullableNotNullFix(PsiModifierListOwner owner, String annotationToAdd,
List<String> annotationsToRemove) {
private static @Nullable AddAnnotationPsiFix createAddNullableNotNullFix(PsiModifierListOwner owner, String annotationToAdd,
List<String> annotationsToRemove) {
if (!isNullabilityAnnotationApplicable(owner)) return null;
return new AddAnnotationPsiFix(annotationToAdd, owner, PsiNameValuePair.EMPTY_ARRAY, ArrayUtilRt.toStringArray(annotationsToRemove));
}
@@ -142,7 +142,7 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI
PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)expression;
PsiLambdaExpression lambda = LambdaRefactoringUtil.createLambda(methodRef, true);
if (lambda != null) {
LambdaRefactoringUtil.specifyLambdaParameterTypes(methodRef.getFunctionalInterfaceType(), lambda);
LambdaUtil.specifyLambdaParameterTypes(methodRef.getFunctionalInterfaceType(), lambda);
return lambda;
}
}
@@ -328,7 +328,7 @@ public abstract class FunctionHelper {
public String tryLightTransform() {
PsiLambdaExpression lambdaExpression = LambdaRefactoringUtil.createLambda(myMethodRef, true);
if(lambdaExpression == null) return null;
String typedParamList = LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(myType, lambdaExpression, false);
String typedParamList = LambdaUtil.createLambdaParameterListWithFormalTypes(myType, lambdaExpression, false);
if(typedParamList != null && lambdaExpression.getBody() != null) {
lambdaExpression = (PsiLambdaExpression)JavaPsiFacade.getElementFactory(myMethodRef.getProject())
.createExpressionFromText(typedParamList + "->" + lambdaExpression.getBody().getText(), myMethodRef);
@@ -13,7 +13,6 @@ import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
@@ -72,7 +71,7 @@ public class LambdaRefactoringUtil {
final PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType();
boolean needToSpecifyFormalTypes = !doNotAddParameterTypes && !isInferredSameTypeAfterConversion(lambdaExpression, referenceExpression);
if (needToSpecifyFormalTypes) {
PsiParameterList typedParamList = specifyLambdaParameterTypes(functionalInterfaceType, lambdaExpression);
PsiParameterList typedParamList = LambdaUtil.specifyLambdaParameterTypes(functionalInterfaceType, lambdaExpression);
if (typedParamList == null) {
return null;
}
@@ -285,52 +284,6 @@ public class LambdaRefactoringUtil {
return false;
}
public static @Nullable String createLambdaParameterListWithFormalTypes(PsiType functionalInterfaceType,
PsiLambdaExpression lambdaExpression,
boolean checkApplicability) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final StringBuilder buf = new StringBuilder();
buf.append("(");
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
LOG.assertTrue(interfaceMethod != null);
final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters();
final PsiParameter[] lambdaParameters = lambdaExpression.getParameterList().getParameters();
if (parameters.length != lambdaParameters.length) return null;
final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult);
for (int i = 0; i < parameters.length; i++) {
PsiType psiType = substitutor.substitute(parameters[i].getType());
if (psiType == null) return null;
if (!PsiTypesUtil.isDenotableType(psiType, lambdaExpression)) {
return null;
}
buf.append(checkApplicability ? psiType.getPresentableText() : psiType.getCanonicalText())
.append(" ")
.append(lambdaParameters[i].getName());
if (i < parameters.length - 1) {
buf.append(", ");
}
}
buf.append(")");
return buf.toString();
}
public static @Nullable PsiParameterList specifyLambdaParameterTypes(PsiLambdaExpression lambdaExpression) {
return specifyLambdaParameterTypes(lambdaExpression.getFunctionalInterfaceType(), lambdaExpression);
}
public static @Nullable PsiParameterList specifyLambdaParameterTypes(PsiType functionalInterfaceType,
@NotNull PsiLambdaExpression lambdaExpression) {
String typedParamList = createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false);
if (typedParamList != null) {
PsiParameterList paramListWithFormalTypes = JavaPsiFacade.getElementFactory(lambdaExpression.getProject())
.createMethodFromText("void foo" + typedParamList, lambdaExpression).getParameterList();
return (PsiParameterList)JavaCodeStyleManager.getInstance(lambdaExpression.getProject())
.shortenClassReferences(lambdaExpression.getParameterList().replace(paramListWithFormalTypes));
}
return null;
}
public static void simplifyToExpressionLambda(@NotNull PsiLambdaExpression lambdaExpression) {
final PsiElement body = lambdaExpression.getBody();
final PsiExpression singleExpression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(body);
@@ -1100,4 +1100,50 @@ public class LambdaUtil {
}
return actualClass;
}
public static @Nullable String createLambdaParameterListWithFormalTypes(PsiType functionalInterfaceType,
PsiLambdaExpression lambdaExpression,
boolean checkApplicability) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final StringBuilder buf = new StringBuilder();
buf.append("(");
final PsiMethod interfaceMethod = getFunctionalInterfaceMethod(functionalInterfaceType);
if (interfaceMethod == null) return null;
final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters();
final PsiParameter[] lambdaParameters = lambdaExpression.getParameterList().getParameters();
if (parameters.length != lambdaParameters.length) return null;
final PsiSubstitutor substitutor = getSubstitutor(interfaceMethod, resolveResult);
for (int i = 0; i < parameters.length; i++) {
PsiType psiType = substitutor.substitute(parameters[i].getType());
if (psiType == null) return null;
if (!PsiTypesUtil.isDenotableType(psiType, lambdaExpression)) {
return null;
}
buf.append(checkApplicability ? psiType.getPresentableText() : psiType.getCanonicalText())
.append(" ")
.append(lambdaParameters[i].getName());
if (i < parameters.length - 1) {
buf.append(", ");
}
}
buf.append(")");
return buf.toString();
}
public static @Nullable PsiParameterList specifyLambdaParameterTypes(PsiLambdaExpression lambdaExpression) {
return specifyLambdaParameterTypes(lambdaExpression.getFunctionalInterfaceType(), lambdaExpression);
}
public static @Nullable PsiParameterList specifyLambdaParameterTypes(PsiType functionalInterfaceType,
@NotNull PsiLambdaExpression lambdaExpression) {
String typedParamList = createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false);
if (typedParamList != null) {
PsiParameterList paramListWithFormalTypes = JavaPsiFacade.getElementFactory(lambdaExpression.getProject())
.createMethodFromText("void foo" + typedParamList, lambdaExpression).getParameterList();
return (PsiParameterList)JavaCodeStyleManager.getInstance(lambdaExpression.getProject())
.shortenClassReferences(lambdaExpression.getParameterList().replace(paramListWithFormalTypes));
}
return null;
}
}
@@ -0,0 +1,8 @@
import org.jetbrains.annotations.NonNls;
// "Annotate parameter 'sb' as @NonNls" "true"
class Foo {
java.util.function.Consumer<StringBuilder> consumerTest() {
return (@NonNls var sb) -> sb.append("foo");
}
}
@@ -0,0 +1,8 @@
import org.jetbrains.annotations.NonNls;
// "Annotate parameter 'sb' as @NonNls" "true"
class Foo {
java.util.function.Consumer<StringBuilder> consumerTest() {
return (@NonNls StringBuilder sb) -> sb.append("foo");
}
}
@@ -0,0 +1,6 @@
// "Annotate parameter 'sb' as @NonNls" "true"
class Foo {
java.util.function.Consumer<StringBuilder> consumerTest() {
return sb -> sb.append("<caret>foo");
}
}
@@ -0,0 +1,6 @@
// "Annotate parameter 'sb' as @NonNls" "true"
class Foo {
java.util.function.Consumer<StringBuilder> consumerTest() {
return sb -> sb.append("<caret>foo");
}
}
@@ -19,11 +19,12 @@ import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCa
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.i18n.I18nInspection;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.impl.PsiDocumentManagerBase;
import com.intellij.testFramework.IdeaTestUtil;
import org.jetbrains.annotations.NotNull;
/**
@@ -66,7 +67,7 @@ public class I18nQuickFixTest extends LightQuickFixParameterizedTestCase {
}
@Override
protected LanguageLevel getLanguageLevel() {
return LanguageLevel.JDK_1_5;
protected Sdk getProjectJDK() {
return IdeaTestUtil.getMockJdk18();
}
}
@@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
@@ -62,8 +61,8 @@ public class LambdaParameterTypeCanBeSpecifiedInspection extends BaseInspection
final PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
if (functionalInterfaceType != null &&
LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType) != null) {
final String inferredTypesText = LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression,
true);
final String inferredTypesText = LambdaUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression,
true);
if (inferredTypesText != null) {
PsiElement nextElement = PsiTreeUtil.skipWhitespacesAndCommentsForward(parameterList);
if (PsiUtil.isJavaToken(nextElement, JavaTokenType.ARROW)) {
@@ -100,7 +99,7 @@ public class LambdaParameterTypeCanBeSpecifiedInspection extends BaseInspection
protected void doFix(Project project, ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (element instanceof PsiLambdaExpression) {
LambdaRefactoringUtil.specifyLambdaParameterTypes((PsiLambdaExpression)element);
LambdaUtil.specifyLambdaParameterTypes((PsiLambdaExpression)element);
}
}
}
@@ -21,7 +21,6 @@ import com.intellij.refactoring.extractMethod.ExtractMethodProcessor;
import com.intellij.refactoring.extractMethod.PrepareFailedException;
import com.intellij.refactoring.rename.RenamePsiElementProcessor;
import com.intellij.refactoring.rename.inplace.MemberInplaceRenamer;
import com.intellij.refactoring.util.LambdaRefactoringUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.duplicates.Match;
import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler;
@@ -64,7 +63,7 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti
}
//can types be specified
if (LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) == null) {
if (LambdaUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) == null) {
return false;
}
@@ -102,7 +101,7 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti
PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
String parameters =
LambdaRefactoringUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) + "{}";
LambdaUtil.createLambdaParameterListWithFormalTypes(functionalInterfaceType, lambdaExpression, false) + "{}";
String targetMethodName = getUniqueMethodName(targetClass, elementFactory, functionalInterfaceType, parameters);
PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression);