mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 02:09:59 +07:00
add fix to create field in anonymous class for lambdas: IDEA-189928
This commit is contained in:
@@ -349,4 +349,6 @@ create.service.implementation.fix.family.name=Create service implementation clas
|
||||
create.service.implementation.fix.name=Create class ''{0}''
|
||||
|
||||
create.service.interface.fix.family.name=Create service
|
||||
create.service.interface.fix.name=Create service ''{0}''
|
||||
create.service.interface.fix.name=Create service ''{0}''
|
||||
|
||||
convert.variable.to.field.in.anonymous.class.fix.name=Convert ''{0}'' to field in anonymous class
|
||||
@@ -1174,5 +1174,8 @@
|
||||
<className>com.intellij.codeInsight.intention.impl.SplitFilterAction</className>
|
||||
<category>Java/Streams</category>
|
||||
</intentionAction>
|
||||
<java.error.fix errorCode="lambda.variable.must.be.final"
|
||||
implementationClass="com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassJava10Fix"
|
||||
/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright 2000-2018 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.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.HighPriorityAction;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.util.RefactoringUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class VariableAccessFromInnerClassJava10Fix extends BaseIntentionAction implements HighPriorityAction {
|
||||
private final PsiElement myContext;
|
||||
|
||||
public VariableAccessFromInnerClassJava10Fix(PsiElement context) {
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Make final";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
if (!myContext.isValid()) return false;
|
||||
PsiReferenceExpression reference = tryCast(myContext, PsiReferenceExpression.class);
|
||||
if (reference == null) return false;
|
||||
PsiLocalVariable variable = tryCast(reference.resolve(), PsiLocalVariable.class);
|
||||
if (variable == null) return false;
|
||||
String name = variable.getName();
|
||||
if (name == null) return false;
|
||||
setText(QuickFixBundle.message("convert.variable.to.field.in.anonymous.class.fix.name", name));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
if (!FileModificationService.getInstance().preparePsiElementsForWrite(myContext)) return;
|
||||
WriteCommandAction.runWriteCommandAction(project, () -> {
|
||||
if (myContext instanceof PsiReferenceExpression && myContext.isValid()) {
|
||||
PsiReferenceExpression referenceExpression = (PsiReferenceExpression)myContext;
|
||||
PsiLocalVariable variable = tryCast(referenceExpression.resolve(), PsiLocalVariable.class);
|
||||
if (variable == null) return;
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
final String variableText = getFieldText(variable, factory, initializer);
|
||||
|
||||
|
||||
PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(myContext, PsiLambdaExpression.class);
|
||||
if (lambdaExpression == null) return;
|
||||
DeclarationInfo declarationInfo = DeclarationInfo.findExistingAnonymousClass(variable, lambdaExpression);
|
||||
if (declarationInfo != null) {
|
||||
replaceReferences(variable, factory, declarationInfo.name);
|
||||
declarationInfo.replace(variableText);
|
||||
variable.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String boxName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("ref", variable, true);
|
||||
String boxDeclarationText = "var " +
|
||||
boxName +
|
||||
" = new Object() { " +
|
||||
variableText +
|
||||
" };";
|
||||
PsiStatement boxDeclaration = factory.createStatementFromText(boxDeclarationText, variable);
|
||||
replaceReferences(variable, factory, boxName);
|
||||
variable.replace(boxDeclaration);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void replaceReferences(PsiLocalVariable variable, PsiElementFactory factory, String boxName) {
|
||||
List<PsiReferenceExpression> references = findReferences(variable);
|
||||
PsiExpression expr = factory.createExpressionFromText(boxName + "." + variable.getName(), null);
|
||||
for (PsiReferenceExpression reference : references) {
|
||||
reference.replace(expr);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFieldText(PsiLocalVariable variable, PsiElementFactory factory, PsiExpression initializer) {
|
||||
// var x is not allowed as field
|
||||
if (initializer != null && variable.getTypeElement().isInferredType() && initializer.getType() != null) {
|
||||
PsiLocalVariable copy = (PsiLocalVariable)variable.copy();
|
||||
copy.getTypeElement().replace(factory.createTypeElement(initializer.getType()));
|
||||
return copy.getText();
|
||||
}
|
||||
else {
|
||||
return variable.getText();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeclarationInfo {
|
||||
final boolean isBefore;
|
||||
final @NotNull PsiStatement myStatementToReplace;
|
||||
final @NotNull PsiAnonymousClass myAnonymousClass;
|
||||
final @NotNull PsiNewExpression myNewExpression;
|
||||
final @NotNull PsiLocalVariable myVariable;
|
||||
final @NotNull String name;
|
||||
|
||||
public DeclarationInfo(boolean isBefore,
|
||||
@NotNull PsiStatement statementToReplace,
|
||||
@NotNull PsiAnonymousClass anonymousClass,
|
||||
@NotNull PsiNewExpression expression, @NotNull PsiLocalVariable variable, @NotNull String name) {
|
||||
this.isBefore = isBefore;
|
||||
myStatementToReplace = statementToReplace;
|
||||
myAnonymousClass = anonymousClass;
|
||||
myNewExpression = expression;
|
||||
myVariable = variable;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static DeclarationInfo findExistingAnonymousClass(@NotNull PsiVariable variable, @NotNull PsiLambdaExpression lambdaExpression) {
|
||||
PsiElement varDeclarationStatement = RefactoringUtil.getParentStatement(variable, false);
|
||||
if (varDeclarationStatement == null) return null;
|
||||
PsiStatement nextStatement = PsiTreeUtil.getNextSiblingOfType(varDeclarationStatement, PsiStatement.class);
|
||||
PsiDeclarationStatement nextDeclarationStatement = tryCast(nextStatement, PsiDeclarationStatement.class);
|
||||
DeclarationInfo nextDeclaration = findExistingAnonymousClass(variable, lambdaExpression, nextDeclarationStatement, true);
|
||||
if (nextDeclaration != null) return nextDeclaration;
|
||||
PsiDeclarationStatement previousDeclarationStatement = PsiTreeUtil.getPrevSiblingOfType(varDeclarationStatement, PsiDeclarationStatement.class);
|
||||
return findExistingAnonymousClass(variable, lambdaExpression, previousDeclarationStatement, false);
|
||||
}
|
||||
|
||||
void replace(@NotNull String variableText) {
|
||||
PsiLocalVariable localVariable = (PsiLocalVariable)myVariable.copy();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(localVariable.getProject());
|
||||
PsiElement lBrace = myAnonymousClass.getLBrace();
|
||||
PsiElement rBrace = myAnonymousClass.getRBrace();
|
||||
if (lBrace == null || rBrace == null) return;
|
||||
PsiElement rBracePrev = rBrace.getPrevSibling();
|
||||
if (rBracePrev == null) return;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (PsiElement element : myAnonymousClass.getChildren()) {
|
||||
sb.append(element.getText());
|
||||
if (isBefore && element == lBrace || !isBefore && element == rBracePrev) {
|
||||
sb.append(variableText);
|
||||
}
|
||||
}
|
||||
localVariable.setInitializer(factory.createExpressionFromText("new " + sb.toString(), myVariable));
|
||||
myStatementToReplace.replace(factory.createStatementFromText(localVariable.getText() + ";", localVariable));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DeclarationInfo findExistingAnonymousClass(@NotNull PsiVariable variable,
|
||||
@NotNull PsiLambdaExpression lambdaExpression,
|
||||
@Nullable PsiDeclarationStatement declarationStatement,
|
||||
boolean isBefore) {
|
||||
if (declarationStatement == null) return null;
|
||||
PsiElement[] declaredElements = declarationStatement.getDeclaredElements();
|
||||
if (declaredElements.length != 1) return null;
|
||||
PsiLocalVariable localVariable = tryCast(declaredElements[0], PsiLocalVariable.class);
|
||||
if (localVariable == null) return null;
|
||||
String name = localVariable.getName();
|
||||
if (name == null) return null;
|
||||
PsiNewExpression newExpression = tryCast(localVariable.getInitializer(), PsiNewExpression.class);
|
||||
if (newExpression == null) return null;
|
||||
PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
|
||||
if (anonymousClass == null) return null;
|
||||
PsiElement outerCodeBlock = PsiUtil.getVariableCodeBlock(variable, null);
|
||||
if (outerCodeBlock == null) return null;
|
||||
ReferenceUsageVisitor visitor = new ReferenceUsageVisitor(localVariable, lambdaExpression);
|
||||
outerCodeBlock.accept(visitor);
|
||||
if (visitor.foundOutsideOfLambdaUsage) return null;
|
||||
return new DeclarationInfo(isBefore, declarationStatement, anonymousClass, newExpression, localVariable, name);
|
||||
}
|
||||
|
||||
private static class ReferenceUsageVisitor extends JavaRecursiveElementVisitor {
|
||||
private final PsiLocalVariable myLocalVariable;
|
||||
private final PsiLambdaExpression myLambdaExpression;
|
||||
private boolean foundOutsideOfLambdaUsage;
|
||||
|
||||
public ReferenceUsageVisitor(PsiLocalVariable localVariable, PsiLambdaExpression lambdaExpression) {
|
||||
myLocalVariable = localVariable;
|
||||
myLambdaExpression = lambdaExpression;
|
||||
foundOutsideOfLambdaUsage = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
super.visitReferenceExpression(expression);
|
||||
if (foundOutsideOfLambdaUsage) return;
|
||||
if (ExpressionUtils.isReferenceTo(expression, myLocalVariable)) {
|
||||
if (!isParent(expression, myLambdaExpression)) {
|
||||
foundOutsideOfLambdaUsage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isParent(@NotNull PsiElement place, @NotNull PsiElement parent) {
|
||||
while (place != null) {
|
||||
if (place == parent) {
|
||||
return true;
|
||||
}
|
||||
place = place.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<PsiReferenceExpression> findReferences(@NotNull PsiLocalVariable variable) {
|
||||
PsiElement outerCodeBlock = PsiUtil.getVariableCodeBlock(variable, null);
|
||||
if (outerCodeBlock == null) return emptyList();
|
||||
List<PsiReferenceExpression> references = new SmartList<>();
|
||||
outerCodeBlock.accept(new JavaRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
if (expression.resolve() == variable) {
|
||||
references.add(expression);
|
||||
}
|
||||
}
|
||||
});
|
||||
return references;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
var ref = new Object() {
|
||||
int y = 23;
|
||||
int x = 12;
|
||||
};
|
||||
Runnable r = () -> {
|
||||
ref.x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
var ref = new Object() {
|
||||
int x = 12;
|
||||
int y = 23;
|
||||
};
|
||||
Runnable r = () -> {
|
||||
ref.x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
var ref1 = new Object() {
|
||||
int x = 12;
|
||||
};
|
||||
var ref = new Object() {
|
||||
int y = 23;
|
||||
}
|
||||
ref.y++;
|
||||
Runnable r = () -> {
|
||||
ref1.x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
var ref = new Object() {
|
||||
int x = 12;
|
||||
};
|
||||
Runnable r = () -> {
|
||||
ref.x++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
var ref = new Object() {
|
||||
int y = 23;
|
||||
}
|
||||
int x = 12;
|
||||
Runnable r = () -> {
|
||||
<caret>x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
int x = 12;
|
||||
var ref = new Object() {
|
||||
int y = 23;
|
||||
}
|
||||
Runnable r = () -> {
|
||||
<caret>x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
int x = 12;
|
||||
var ref = new Object() {
|
||||
int y = 23;
|
||||
}
|
||||
ref.y++;
|
||||
Runnable r = () -> {
|
||||
<caret>x++;
|
||||
ref.y++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Convert 'x' to field in anonymous class" "true"
|
||||
class Test {
|
||||
public void test() {
|
||||
int x = 12;
|
||||
Runnable r = () -> {
|
||||
x<caret>++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Convert 'x' to field in anonymous class" "false"
|
||||
class Test {
|
||||
public void test(int x) {
|
||||
Runnable r = () -> {
|
||||
x<caret>++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Convert 'x' to field in anonymous class" "false"
|
||||
class Test {
|
||||
public void test() {
|
||||
Runnable r = () -> {
|
||||
x<caret>++;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
// Copyright 2000-2018 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.java.codeInsight.daemon.quickFix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
|
||||
public class VariableAccessFromInnerClassJava10Test extends LightQuickFixParameterizedTestCase {
|
||||
|
||||
public void test() {
|
||||
doAllTests();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/mustBeFinalJava10";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LanguageLevel getLanguageLevel() {
|
||||
return LanguageLevel.JDK_10;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user