[java-intentions] Implement quick-fix to create record from deconstruction pattern, if the identifier is unresolved

IDEA-303300

GitOrigin-RevId: f4cd752f81987885a72b5da8d898f875a5198dcb
This commit is contained in:
Andrey.Cherkasov
2022-10-10 19:40:42 +04:00
committed by intellij-monorepo-bot
parent 82f6661d34
commit 375b7b102f
20 changed files with 297 additions and 63 deletions

View File

@@ -62,6 +62,7 @@ public abstract class CreateClassFromUsageBaseFix extends BaseIntentionAction {
if (parent instanceof PsiTypeElement) {
if (parent.getParent() instanceof PsiReferenceParameterList) return true;
if (parent.getParent() instanceof PsiDeconstructionPattern) return true;
while (parent.getParent() instanceof PsiTypeElement){
parent = parent.getParent();

View File

@@ -37,6 +37,9 @@ public class CreateClassFromUsageFix extends CreateClassFromUsageBaseFix {
CreateFromUsageUtils.setupSuperClassReference(aClass, superClassName);
}
CreateFromUsageBaseFix.setupGenericParameters(aClass, element);
if (element.getParent() instanceof PsiTypeElement typeElement && typeElement.getParent() instanceof PsiDeconstructionPattern pattern) {
CreateInnerClassFromUsageFix.setupRecordFromDeconstructionPattern(aClass, pattern, getText());
}
CodeStyleManager.getInstance(project).reformat(aClass);
return new IntentionPreviewInfo.CustomDiff(JavaFileType.INSTANCE, "", aClass.getText());
}
@@ -69,11 +72,15 @@ public class CreateClassFromUsageFix extends CreateClassFromUsageBaseFix {
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
Navigatable descriptor = PsiNavigationSupport.getInstance().createNavigatable(refElement.getProject(),
aClass.getContainingFile()
.getVirtualFile(),
aClass.getTextOffset());
descriptor.navigate(true);
if (element.getParent() instanceof PsiTypeElement typeElement &&
typeElement.getParent() instanceof PsiDeconstructionPattern pattern) {
CreateInnerClassFromUsageFix.setupRecordFromDeconstructionPattern(aClass, pattern, getText());
}
else {
Navigatable descriptor = PsiNavigationSupport.getInstance()
.createNavigatable(refElement.getProject(), aClass.getContainingFile().getVirtualFile(), aClass.getTextOffset());
descriptor.navigate(true);
}
}
);
}

View File

@@ -15,22 +15,30 @@
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInsight.template.Template;
import com.intellij.codeInsight.template.TemplateBuilderImpl;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.ide.util.PsiClassListCellRenderer;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.IPopupChooserBuilder;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Segment;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.CommonJavaRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
@@ -156,12 +164,60 @@ public class CreateInnerClassFromUsageFix extends CreateClassFromUsageBaseFix {
CreateFromUsageBaseFix.setupGenericParameters(created, ref);
if (!aClass.isPhysical()) {
aClass.add(created);
} else {
PsiClass add = (PsiClass)aClass.add(created);
if (ref.getParent() instanceof PsiTypeElement typeElement &&
typeElement.getParent() instanceof PsiDeconstructionPattern pattern) {
setupRecordFromDeconstructionPattern(add, pattern, getText());
}
}
else {
if (!FileModificationService.getInstance().preparePsiElementForWrite(aClass)) return;
WriteCommandAction.runWriteCommandAction(aClass.getProject(), getText(), null,
() -> ref.bindToElement(aClass.add(created)),
() -> {
PsiClass add = (PsiClass)aClass.add(created);
ref.bindToElement(add);
if (ref.getParent() instanceof PsiTypeElement typeElement &&
typeElement.getParent() instanceof PsiDeconstructionPattern pattern) {
setupRecordFromDeconstructionPattern(add, pattern, getText());
}
},
aClass.getContainingFile());
}
}
static void setupRecordFromDeconstructionPattern(@Nullable PsiClass aClass, final @NotNull PsiDeconstructionPattern pattern,
@IntentionName @NotNull String text) {
if (aClass == null) return;
final PsiJavaCodeReferenceElement classReference = pattern.getTypeElement().getInnermostComponentReferenceElement();
if (classReference != null && aClass.isPhysical()) {
classReference.bindToElement(aClass);
}
PsiDeconstructionList deconstructionList = pattern.getDeconstructionList();
final Project project = aClass.getProject();
if (deconstructionList.getDeconstructionComponents().length != 0) {
TemplateBuilderImpl templateBuilder = createRecordHeaderTemplate(aClass, deconstructionList);
aClass = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(aClass);
final Template template = templateBuilder.buildTemplate();
template.setToReformat(true);
final Editor editor = CreateFromUsageBaseFix.positionCursor(project, aClass.getContainingFile(), aClass);
if (editor == null) return;
Segment textRange = aClass.getTextRange();
editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset());
CreateFromUsageBaseFix.startTemplate(editor, template, project, null, text);
}
else {
CodeInsightUtil.positionCursor(project, aClass.getContainingFile(), ObjectUtils.notNull(aClass.getNameIdentifier(), aClass));
}
}
private static @NotNull TemplateBuilderImpl createRecordHeaderTemplate(PsiClass aClass, PsiDeconstructionList list) {
TemplateBuilderImpl templateBuilder = new TemplateBuilderImpl(aClass);
PsiRecordHeader header = aClass.getRecordHeader();
CreateRecordFromNewFix.setupRecordComponents(header, templateBuilder, list, PsiSubstitutor.EMPTY);
return templateBuilder;
}
}

View File

@@ -14,6 +14,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.CommonJavaRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
@@ -22,6 +23,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -45,11 +47,23 @@ public class CreateRecordFromNewFix extends CreateClassFromNewFix {
return templateBuilder;
}
/**
* @param header header to set up record components
* @param builder builder to use
* @param list <ul>
* <li>{@code PsiExpressionList} if the record is created from a new expression</li>
* <li>{@code PsiDeconstructionList} if the record is created from a record pattern</li>
* </ul>
* @param substitutor substitutor to use
* @throws IncorrectOperationException if setting up of record components fails
*/
static void setupRecordComponents(@Nullable PsiRecordHeader header, @NotNull TemplateBuilder builder,
@NotNull PsiExpressionList argumentList, @NotNull PsiSubstitutor substitutor)
@NotNull PsiElement list, @NotNull PsiSubstitutor substitutor)
throws IncorrectOperationException {
if (header == null) return;
PsiExpression[] args = argumentList.getExpressions();
if (header == null || !(list instanceof PsiExpressionList || list instanceof PsiDeconstructionList)) return;
PsiCaseLabelElement[] elements = list instanceof PsiExpressionList argumentList
? argumentList.getExpressions()
: ((PsiDeconstructionList)list).getDeconstructionComponents();
final PsiManager psiManager = header.getManager();
final Project project = psiManager.getProject();
@@ -75,20 +89,19 @@ public class CreateRecordFromNewFix extends CreateClassFromNewFix {
}
List<ComponentData> components = new ArrayList<>();
//255 is the maximum number of record components
for (int i = 0; i < Math.min(args.length, 255); i++) {
PsiExpression exp = args[i];
PsiType argType = CommonJavaRefactoringUtil.getTypeByExpression(exp);
SuggestedNameInfo suggestedInfo = JavaCodeStyleManager.getInstance(project).suggestVariableName(
VariableKind.PARAMETER, null, exp, argType);
@NonNls String[] names = suggestedInfo.names;
for (int i = 0; i < Math.min(elements.length, 255); i++) {
PsiCaseLabelElement element = elements[i];
PsiType type = element instanceof PsiExpression expression
? CommonJavaRefactoringUtil.getTypeByExpression(expression)
: JavaPsiPatternUtil.getPatternType(element);
@NonNls String[] names = suggestVariableName(project, element, type).names;
if (names.length == 0) {
names = new String[]{"c" + i};
}
argType = CreateFromUsageUtils.getParameterTypeByArgumentType(argType, psiManager, resolveScope);
components.add(new ComponentData(argType, names));
type = CreateFromUsageUtils.getParameterTypeByArgumentType(type, psiManager, resolveScope);
components.add(new ComponentData(type, names));
}
PsiRecordHeader newHeader = factory.createRecordHeaderFromText(StringUtil.join(components, ", "), containingClass);
PsiRecordHeader replacedHeader = (PsiRecordHeader)header.replace(newHeader);
@@ -100,11 +113,22 @@ public class CreateRecordFromNewFix extends CreateClassFromNewFix {
ExpectedTypeInfo info = ExpectedTypesProvider.createInfo(data.myType, ExpectedTypeInfo.TYPE_OR_SUPERTYPE, data.myType, TailType.NONE);
PsiElement context = PsiTreeUtil.getParentOfType(argumentList, PsiClass.class, PsiMethod.class);
PsiElement context = PsiTreeUtil.getParentOfType(list, PsiClass.class, PsiMethod.class);
guesser.setupTypeElement(Objects.requireNonNull(component.getTypeElement()), new ExpectedTypeInfo[]{info}, context, containingClass);
Expression expression = new CreateFromUsageUtils.ParameterNameExpression(data.myNames);
builder.replaceElement(Objects.requireNonNull(component.getNameIdentifier()), expression);
}
}
private static SuggestedNameInfo suggestVariableName(@NotNull Project project,
@NotNull PsiCaseLabelElement element,
@Nullable PsiType type) {
if (element instanceof PsiExpression expression) {
return JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, null, expression, type);
}
PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(element);
List<String> semanticNames = variable != null ? Collections.singletonList(variable.getName()) : Collections.emptyList();
return JavaCodeStyleManager.getInstance(project).suggestNames(semanticNames, VariableKind.PARAMETER, type);
}
}

View File

@@ -27,8 +27,7 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
@Override
public void registerFixes(@NotNull PsiJavaCodeReferenceElement ref, @NotNull QuickFixActionRegistrar registrar) {
PsiFile containingFile = ref.getContainingFile();
if (containingFile instanceof PsiJavaCodeReferenceCodeFragment &&
!((PsiJavaCodeReferenceCodeFragment)containingFile).isClassesAccepted()) {
if (containingFile instanceof PsiJavaCodeReferenceCodeFragment fragment && !fragment.isClassesAccepted()) {
return;
}
if (PsiUtil.isModuleFile(containingFile)) {
@@ -49,12 +48,10 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
MoveClassToModuleFix.registerFixes(registrar, ref);
if (ref instanceof PsiReferenceExpression) {
if (ref instanceof PsiReferenceExpression refExpr) {
TextRange fixRange = HighlightMethodUtil.getFixRange(ref);
PsiReferenceExpression refExpr = (PsiReferenceExpression)ref;
registrar.register(fixRange, new RenameWrongRefFix(refExpr), null);
PsiExpression qualifier = ((PsiReferenceExpression)ref).getQualifierExpression();
PsiExpression qualifier = refExpr.getQualifierExpression();
if (qualifier != null) {
AddTypeCastFix.registerFix(registrar, qualifier, ref, fixRange);
}
@@ -68,42 +65,8 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
}
}
PsiElement parent = PsiTreeUtil.getParentOfType(ref, PsiNewExpression.class, PsiMethod.class);
PsiExpressionList expressionList = PsiTreeUtil.getParentOfType(ref, PsiExpressionList.class);
boolean isNewExpression =
parent instanceof PsiNewExpression &&
!(refParent instanceof PsiTypeElement) &&
(expressionList == null || !PsiTreeUtil.isAncestor(parent, expressionList, false));
if (isNewExpression) {
registrar.register(new CreateClassFromNewFix((PsiNewExpression)parent));
}
else {
registrar.register(new CreateClassFromUsageFix(ref, CreateClassKind.CLASS));
}
registrar.register(new CreateClassFromUsageFix(ref, CreateClassKind.INTERFACE));
if (PsiUtil.isLanguageLevel5OrHigher(ref)) {
registrar.register(new CreateClassFromUsageFix(ref, CreateClassKind.ENUM));
registrar.register(new CreateClassFromUsageFix(ref, CreateClassKind.ANNOTATION));
}
if (HighlightingFeature.RECORDS.isAvailable(ref)) {
if (isNewExpression) {
registrar.register(new CreateRecordFromNewFix((PsiNewExpression)parent));
}
else {
registrar.register(new CreateClassFromUsageFix(ref, CreateClassKind.RECORD));
}
}
if (isNewExpression) {
registrar.register(new CreateInnerClassFromNewFix((PsiNewExpression)parent));
if (HighlightingFeature.RECORDS.isAvailable(ref) && ((PsiNewExpression)parent).getQualifier() == null) {
registrar.register(new CreateInnerRecordFromNewFix((PsiNewExpression)parent));
}
}
else {
registrar.register(new CreateInnerClassFromUsageFix(ref, CreateClassKind.CLASS));
for (IntentionAction action : createClassActions(ref)) {
registrar.register(action);
}
SurroundWithQuotesAnnotationParameterValueFix.register(registrar, ref);
@@ -113,6 +76,57 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider
}
}
@NotNull
private static Collection<IntentionAction> createClassActions(@NotNull PsiJavaCodeReferenceElement ref) {
Collection<IntentionAction> result = new ArrayList<>();
PsiElement refParent = ref.getParent();
if (refParent != null && refParent.getParent() instanceof PsiDeconstructionPattern) {
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.RECORD));
result.add(new CreateInnerClassFromUsageFix(ref, CreateClassKind.RECORD));
}
else {
PsiElement parent = PsiTreeUtil.getParentOfType(ref, PsiNewExpression.class, PsiMethod.class);
PsiExpressionList expressionList = PsiTreeUtil.getParentOfType(ref, PsiExpressionList.class);
boolean isNewExpression =
parent instanceof PsiNewExpression &&
!(refParent instanceof PsiTypeElement) &&
(expressionList == null || !PsiTreeUtil.isAncestor(parent, expressionList, false));
if (isNewExpression) {
result.add(new CreateClassFromNewFix((PsiNewExpression)parent));
}
else {
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.CLASS));
}
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.INTERFACE));
if (PsiUtil.isLanguageLevel5OrHigher(ref)) {
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.ENUM));
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.ANNOTATION));
}
if (HighlightingFeature.RECORDS.isAvailable(ref)) {
if (isNewExpression) {
result.add(new CreateRecordFromNewFix((PsiNewExpression)parent));
}
else {
result.add(new CreateClassFromUsageFix(ref, CreateClassKind.RECORD));
}
}
if (isNewExpression) {
result.add(new CreateInnerClassFromNewFix((PsiNewExpression)parent));
if (HighlightingFeature.RECORDS.isAvailable(ref) && ((PsiNewExpression)parent).getQualifier() == null) {
result.add(new CreateInnerRecordFromNewFix((PsiNewExpression)parent));
}
}
else {
result.add(new CreateInnerClassFromUsageFix(ref, CreateClassKind.CLASS));
}
}
return result;
}
@NotNull
private static Collection<IntentionAction> createVariableActions(@NotNull PsiReferenceExpression refExpr) {
Collection<IntentionAction> result = new ArrayList<>();

View File

@@ -0,0 +1,12 @@
// "Create record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
}
public record Point(double x, double y) {
}

View File

@@ -0,0 +1,12 @@
// "Create record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
}
public record Point(double x, double y) {
}

View File

@@ -0,0 +1,12 @@
// "Create record 'Rect'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point(double x, double y)) -> {}
default -> {}
}
}
}
public record Rect(Point point1, Point point) {
}

View File

@@ -0,0 +1,9 @@
// "Create record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Poi<caret>nt(double x, double y) -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Create record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Poi<caret>nt(double x, double y) -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Create record 'Rect'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Re<caret>ct(Point point1, Point(double x, double y)) -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,2 @@
public record Point(double x, double y) {
}

View File

@@ -0,0 +1,2 @@
public record Point(double x, double y) {
}

View File

@@ -0,0 +1,2 @@
public record Rect(Point point1, Point point) {
}

View File

@@ -0,0 +1,12 @@
// "Create inner record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
private record Point(double x, double y) {
}
}

View File

@@ -0,0 +1,12 @@
// "Create inner record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Point(double x, double y) -> {}
default -> {}
}
}
private record Point(double x, double y) {
}
}

View File

@@ -0,0 +1,12 @@
// "Create inner record 'Rect'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Rect(Point point1, Point(double x, double y)) -> {}
default -> {}
}
}
private record Rect(Point point1, Point point) {
}
}

View File

@@ -0,0 +1,9 @@
// "Create inner record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Poi<caret>nt(double x, double y) -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Create inner record 'Point'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Poi<caret>nt(double x, double y) -> {}
default -> {}
}
}
}

View File

@@ -0,0 +1,9 @@
// "Create inner record 'Rect'" "true-preview"
class Test {
void foo(Object obj) {
switch (obj) {
case Re<caret>ct(Point point1, Point(double x, double y)) -> {}
default -> {}
}
}
}