mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-229694 Generate constructor for records: suggest generation of compact and canonical constructors
GitOrigin-RevId: 95e23c12711c867a1e1f41d42ee5f3cc87876b3d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
39fc484b5d
commit
84c1a10be9
+56
-1
@@ -2,6 +2,7 @@
|
||||
package com.intellij.codeInsight.generation;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.codeInsight.AnnotationTargetUtil;
|
||||
import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
||||
@@ -12,11 +13,14 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -63,6 +67,15 @@ public class GenerateConstructorHandler extends GenerateMembersHandlerBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (aClass.isRecord() && JavaPsiRecordUtil.findCanonicalConstructor(aClass) == null) {
|
||||
RecordConstructorChooserDialog dialog = new RecordConstructorChooserDialog(aClass);
|
||||
if (!dialog.showAndGet()) return null;
|
||||
ClassMember member = dialog.getClassMember();
|
||||
if (member != null) {
|
||||
return new ClassMember[]{member};
|
||||
}
|
||||
}
|
||||
|
||||
myCopyJavadoc = false;
|
||||
PsiMethod[] baseConstructors = null;
|
||||
PsiClass baseClass = aClass.getSuperClass();
|
||||
@@ -146,10 +159,15 @@ public class GenerateConstructorHandler extends GenerateMembersHandlerBase {
|
||||
@Override
|
||||
@NotNull
|
||||
protected List<? extends GenerationInfo> generateMemberPrototypes(PsiClass aClass, ClassMember[] members) throws IncorrectOperationException {
|
||||
if (members.length == 1 && members[0] instanceof RecordConstructorMember) {
|
||||
boolean compact = ((RecordConstructorMember)members[0]).isCompact();
|
||||
return Collections.singletonList(new PsiGenerationInfo<>(generateRecordConstructor(aClass, compact)));
|
||||
}
|
||||
|
||||
List<PsiMethod> baseConstructors = new ArrayList<>();
|
||||
List<PsiField> fieldsVector = new ArrayList<>();
|
||||
for (ClassMember member1 : members) {
|
||||
PsiElement member = ((PsiElementClassMember)member1).getElement();
|
||||
PsiElement member = ((PsiElementClassMember<?>)member1).getElement();
|
||||
if (member instanceof PsiMethod) {
|
||||
baseConstructors.add((PsiMethod)member);
|
||||
}
|
||||
@@ -179,6 +197,43 @@ public class GenerateConstructorHandler extends GenerateMembersHandlerBase {
|
||||
return filterOutAlreadyInsertedConstructors(aClass, constructors);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiMethod generateRecordConstructor(PsiClass aClass, boolean compact) {
|
||||
String constructor;
|
||||
if (compact) {
|
||||
constructor = "public " + aClass.getName() + "{\n}";
|
||||
}
|
||||
else {
|
||||
PsiRecordComponent[] components = aClass.getRecordComponents();
|
||||
String parameters = StreamEx.of(components).map(PsiRecordComponent::getText).joining(",", "(", ")");
|
||||
String body =
|
||||
StreamEx.of(components).map(PsiRecordComponent::getName).map(name -> "this." + name + "=" + name + ";\n").joining("", "{", "}");
|
||||
constructor = "public " + aClass.getName() + parameters + body;
|
||||
}
|
||||
Project project = aClass.getProject();
|
||||
PsiMethod ctor = JavaPsiFacade.getElementFactory(project).createMethodFromText(constructor, aClass);
|
||||
if (!compact) {
|
||||
JavaCodeStyleSettings settings = JavaCodeStyleSettings.getInstance(aClass.getContainingFile());
|
||||
boolean finalParameters = settings.isGenerateFinalParameters();
|
||||
PsiParameterList parameterList = ctor.getParameterList();
|
||||
for (PsiParameter parameter : parameterList.getParameters()) {
|
||||
PsiModifierList modifierList = parameter.getModifierList();
|
||||
if (modifierList != null) {
|
||||
modifierList.setModifierProperty(PsiModifier.FINAL, finalParameters);
|
||||
PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(modifierList);
|
||||
for (PsiAnnotation annotation : parameter.getAnnotations()) {
|
||||
PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets);
|
||||
if (applicable == null) {
|
||||
annotation.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CodeStyleManager.getInstance(project).reformat(ctor);
|
||||
return ctor;
|
||||
}
|
||||
|
||||
private static List<? extends GenerationInfo> filterOutAlreadyInsertedConstructors(PsiClass aClass, List<? extends GenerationInfo> constructors) {
|
||||
boolean alreadyExist = true;
|
||||
for (GenerationInfo constructor : constructors) {
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.generation;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.daemon.JavaErrorMessages;
|
||||
import com.intellij.openapi.application.ApplicationBundle;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
final class RecordConstructorChooserDialog extends DialogWrapper {
|
||||
@NotNull private final JBRadioButton myCompact;
|
||||
@NotNull private final JBRadioButton myCanonical;
|
||||
@NotNull private final JBRadioButton myCustom;
|
||||
@NotNull private final PsiClass myRecordClass;
|
||||
|
||||
RecordConstructorChooserDialog(@NotNull PsiClass recordClass) {
|
||||
super(recordClass.getProject());
|
||||
myRecordClass = recordClass;
|
||||
setTitle(CodeInsightBundle.message("generate.record.constructor.title"));
|
||||
setOKButtonText(CodeInsightBundle.message("generate.button.title"));
|
||||
myCompact = new JBRadioButton(JavaErrorMessages.message("record.compact.constructor"), true);
|
||||
myCanonical = new JBRadioButton(JavaErrorMessages.message("record.canonical.constructor"), false);
|
||||
myCustom = new JBRadioButton(ApplicationBundle.message("custom.option"), false);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myCompact;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
JPanel panel = new JPanel(new VerticalFlowLayout());
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
group.add(myCompact);
|
||||
group.add(myCanonical);
|
||||
group.add(myCustom);
|
||||
panel.add(myCompact);
|
||||
panel.add(myCanonical);
|
||||
panel.add(myCustom);
|
||||
return panel;
|
||||
}
|
||||
|
||||
ClassMember getClassMember() {
|
||||
if (myCompact.isSelected()) {
|
||||
return new RecordConstructorMember(myRecordClass, true);
|
||||
}
|
||||
if (myCanonical.isSelected()) {
|
||||
return new RecordConstructorMember(myRecordClass, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.generation;
|
||||
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiFormatUtilBase;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class RecordConstructorMember implements ClassMember {
|
||||
private final PsiClass myRecord;
|
||||
private final boolean myCompact;
|
||||
|
||||
public RecordConstructorMember(PsiClass aRecord, boolean compact) {
|
||||
myRecord = aRecord;
|
||||
myCompact = compact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberChooserObject getParentNodeDelegate() {
|
||||
final String text = PsiFormatUtil.formatClass(myRecord, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME);
|
||||
return new PsiDocCommentOwnerMemberChooserObject(myRecord, text, myRecord.getIcon(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTreeNode(SimpleColoredComponent component, JTree tree) {
|
||||
|
||||
}
|
||||
|
||||
public boolean isCompact() {
|
||||
return myCompact;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
return myCompact ? "Compact constructor" : "Canonical constructor";
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
record Test(final int foo, @Anno1 @Anno2 @Anno3 @Anno4 double bar) {
|
||||
public Test(int foo, @Anno2 @Anno3 @Anno4 double bar) {<caret>
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public int foo() {
|
||||
return foo;
|
||||
}
|
||||
}
|
||||
@Target(ElementType.RECORD_COMPONENT)
|
||||
@interface Anno1 { }
|
||||
@Target({ElementType.RECORD_COMPONENT, ElementType.PARAMETER})
|
||||
@interface Anno2 { }
|
||||
@Target({ElementType.PARAMETER})
|
||||
@interface Anno3 { }
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@interface Anno4 { }
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
record R(int x, int y) {
|
||||
public R {<caret>
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
record Test(final int foo, @Anno1 @Anno2 @Anno3 @Anno4 double bar, String... varArg) {
|
||||
<caret>
|
||||
public int foo() {
|
||||
return foo;
|
||||
}
|
||||
}
|
||||
@Target(ElementType.RECORD_COMPONENT)
|
||||
@interface Anno1 { }
|
||||
@Target({ElementType.RECORD_COMPONENT, ElementType.PARAMETER})
|
||||
@interface Anno2 { }
|
||||
@Target({ElementType.PARAMETER})
|
||||
@interface Anno3 { }
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@interface Anno4 { }
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
record R(int x, int y) {
|
||||
<caret>
|
||||
}
|
||||
@@ -19,17 +19,20 @@ import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.generation.ClassMember;
|
||||
import com.intellij.codeInsight.generation.GenerateConstructorHandler;
|
||||
import com.intellij.codeInsight.generation.RecordConstructorMember;
|
||||
import com.intellij.java.codeInspection.DataFlowInspectionTest;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -104,10 +107,27 @@ public class GenerateConstructorTest extends LightJavaCodeInsightFixtureTestCase
|
||||
}
|
||||
|
||||
public void testNullableField() { doTest(); }
|
||||
|
||||
public void testRecordCompactConstructor() { doTestRecordConstructor(true); }
|
||||
|
||||
public void testRecordCanonicalConstructor() { doTestRecordConstructor(false); }
|
||||
|
||||
private void doTest() {
|
||||
doTest(false);
|
||||
}
|
||||
|
||||
private void doTestRecordConstructor(boolean compact) {
|
||||
String name = getTestName(false);
|
||||
myFixture.configureByFile("before" + name + ".java");
|
||||
new GenerateConstructorHandler() {
|
||||
@Nullable
|
||||
@Override
|
||||
protected ClassMember[] chooseOriginalMembers(PsiClass aClass, Project project) {
|
||||
return new ClassMember[]{new RecordConstructorMember(aClass, compact)};
|
||||
}
|
||||
}.invoke(getProject(), getEditor(), getFile());
|
||||
myFixture.checkResultByFile("after" + name + ".java");
|
||||
}
|
||||
|
||||
private void doTest(boolean preSelect) {
|
||||
String name = getTestName(false);
|
||||
|
||||
@@ -898,3 +898,5 @@ light.edit.close.cancel=Cancel
|
||||
|
||||
light.edit.exit.title=Save Documents
|
||||
light.edit.exit.message=There are unsaved documents.\nDo you want to save the changes?
|
||||
|
||||
custom.option=Custom...
|
||||
@@ -50,6 +50,8 @@ dialog.paste.on.import.text3=<html>The code fragment which you have pasted intro
|
||||
copy.paste.reference.notification=<html>{0} {0, choice, 1#import was|2#imports were} added<p><span><a href='show'>Review Added Imports...</a></span></html>
|
||||
paste.data.flavor.folding=FoldingData
|
||||
paste.dataflavor.referencedata=ReferenceData
|
||||
generate.button.title=Generate
|
||||
generate.record.constructor.title=Generate Records Constructor
|
||||
generate.constructor.fields.chooser.title=Choose Fields to Initialize by Constructor
|
||||
error.attempt.to.generate.constructor.for.anonymous.class=Cannot add constructor to an anonymous class
|
||||
generate.constructor.super.constructor.chooser.title=Choose Super Class Constructor
|
||||
|
||||
Reference in New Issue
Block a user