mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
intention to extract lambda body to method and convert to method reference (IDEA-123262)
This commit is contained in:
@@ -522,5 +522,10 @@
|
||||
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
|
||||
<categoryKey>intention.category.other</categoryKey>
|
||||
</intentionAction>
|
||||
<intentionAction>
|
||||
<className>com.siyeh.ipp.functional.ExtractToMethodReferenceIntention</className>
|
||||
<bundleName>com.siyeh.IntentionPowerPackBundle</bundleName>
|
||||
<categoryKey>intention.category.control.flow</categoryKey>
|
||||
</intentionAction>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -2,6 +2,7 @@ plugin.IntentionPowerPack.description=Adds over 80 new intention actions for IDE
|
||||
replace.fully.qualified.name.with.import.intention.name=Replace qualified name with 'import'
|
||||
replace.fully.qualified.name.with.import.intention.family.name=Replace Qualified Name with Import
|
||||
replace.if.with.switch.intention.name=Replace 'if' with 'switch'
|
||||
extract.to.method.reference.intention.name=Extract to method reference
|
||||
replace.if.with.switch.intention.family.name=Replace If with Switch
|
||||
merge.parallel.ifs.intention.name=Merge 'if's
|
||||
merge.parallel.ifs.intention.family.name=Merge Parallel Ifs
|
||||
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ipp.functional;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.codeInsight.intention.BaseElementAtCaretIntentionAction;
|
||||
import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.extractMethod.AbstractExtractDialog;
|
||||
import com.intellij.refactoring.extractMethod.ControlFlowWrapper;
|
||||
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.VariableData;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import com.siyeh.IntentionPowerPackBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntentionAction {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
return getFamilyName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return IntentionPowerPackBundle.message("extract.to.method.reference.intention.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
|
||||
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class, false);
|
||||
if (lambdaExpression != null) {
|
||||
PsiElement body = lambdaExpression.getBody();
|
||||
if (body == null) return false;
|
||||
PsiType functionalInterfaceType = lambdaExpression.getFunctionalInterfaceType();
|
||||
if (functionalInterfaceType == null) return false;
|
||||
PsiExpression asMethodReference = LambdaCanBeMethodReferenceInspection
|
||||
.canBeMethodReferenceProblem(body, lambdaExpression.getParameterList().getParameters(), functionalInterfaceType, null);
|
||||
if (asMethodReference != null) return false;
|
||||
try {
|
||||
PsiElement[] toExtract = body instanceof PsiCodeBlock ? ((PsiCodeBlock)body).getStatements() : new PsiElement[] {body};
|
||||
ControlFlowWrapper wrapper = new ControlFlowWrapper(project, body, toExtract);
|
||||
wrapper.prepareExitStatements(toExtract, body);
|
||||
PsiVariable[] outputVariables = wrapper.getOutputVariables();
|
||||
List<PsiVariable> inputVariables = wrapper.getInputVariables(body, toExtract, outputVariables);
|
||||
return inputVariables.stream().allMatch(variable -> variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() == lambdaExpression);
|
||||
}
|
||||
catch (PrepareFailedException ignored) { }
|
||||
catch (ControlFlowWrapper.ExitStatementsNotSameException ignored) { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
|
||||
final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class, false);
|
||||
if (lambdaExpression != null) {
|
||||
PsiElement body = lambdaExpression.getBody();
|
||||
if (body != null) {
|
||||
PsiElement[] elements = body instanceof PsiCodeBlock ? ((PsiCodeBlock)body).getStatements() : new PsiElement[] {body};
|
||||
PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(lambdaExpression.getFunctionalInterfaceType());
|
||||
String initialMethodName = interfaceMethod != null ? interfaceMethod.getName() : "name";
|
||||
ExtractMethodProcessor methodProcessor = new ExtractMethodProcessor(project, editor, elements, null, getFamilyName(), null, null) {
|
||||
@Override
|
||||
public boolean showDialog() {
|
||||
apply(new MyExtractMethodDialog(myTargetClass, lambdaExpression, myCanBeStatic, initialMethodName));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
methodProcessor.prepare();
|
||||
}
|
||||
catch (PrepareFailedException e) {
|
||||
return;
|
||||
}
|
||||
methodProcessor.showDialog();
|
||||
WriteAction.run(() -> {
|
||||
methodProcessor.doExtract();
|
||||
PsiExpression expression = LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(lambdaExpression);
|
||||
if (expression instanceof PsiMethodReferenceExpression) {
|
||||
PsiMethod method = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(methodProcessor.getExtractedMethod());
|
||||
|
||||
PsiElement refElement = ((PsiMethodReferenceExpression)expression).getReferenceNameElement();
|
||||
PsiIdentifier nameIdentifier = method.getNameIdentifier();
|
||||
if (nameIdentifier == null) return;
|
||||
|
||||
//try to navigate to reference name
|
||||
editor.getCaretModel().moveToOffset(ObjectUtils.notNull(refElement, nameIdentifier).getTextOffset());
|
||||
|
||||
final RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(method);
|
||||
if (!processor.isInplaceRenameSupported()) {
|
||||
return;
|
||||
}
|
||||
List<String> suggestedNames = new ArrayList<>();
|
||||
suggestedNames.add(method.getName());
|
||||
processor.substituteElementToRename(method, editor, new Pass<PsiElement>() {
|
||||
@Override
|
||||
public void pass(PsiElement substitutedElement) {
|
||||
final MemberInplaceRenamer renamer = new MemberInplaceRenamer(method, substitutedElement, editor);
|
||||
final LinkedHashSet<String> nameSuggestions = new LinkedHashSet<>(suggestedNames);
|
||||
renamer.performInplaceRefactoring(nameSuggestions);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
|
||||
return currentFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class MyExtractMethodDialog implements AbstractExtractDialog {
|
||||
private final String myTargetMethodName;
|
||||
private final boolean myCanBeStatic;
|
||||
private final VariableData[] myVariableData;
|
||||
|
||||
public MyExtractMethodDialog(@NotNull PsiClass targetClass,
|
||||
PsiLambdaExpression lambdaExpression,
|
||||
boolean canBeStatic,
|
||||
String initialMethodName) {
|
||||
myVariableData = Arrays.stream(lambdaExpression.getParameterList().getParameters())
|
||||
.map(parameter -> {
|
||||
VariableData data = new VariableData(parameter);
|
||||
data.passAsParameter = true;
|
||||
data.name = parameter.getName();
|
||||
return data;
|
||||
})
|
||||
.toArray(VariableData[]::new);
|
||||
myCanBeStatic = canBeStatic;
|
||||
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(targetClass.getProject());
|
||||
String parameters = "(" + StringUtil.join(myVariableData, data -> data.type.getCanonicalText() + " " + data.name, ", ") + "){}";
|
||||
myTargetMethodName = UniqueNameGenerator.generateUniqueName(initialMethodName,
|
||||
methodName -> {
|
||||
String methodText = "private void " + methodName + parameters;
|
||||
PsiMethod patternMethod = elementFactory.createMethodFromText(methodText, lambdaExpression);
|
||||
return targetClass.findMethodBySignature(patternMethod, true) == null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChosenMethodName() {
|
||||
return myTargetMethodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VariableData[] getChosenParameters() {
|
||||
return myVariableData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getVisibility() {
|
||||
return PsiModifier.PRIVATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMakeStatic() {
|
||||
return myCanBeStatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChainedConstructor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiType getReturnType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {}
|
||||
|
||||
@Override
|
||||
public boolean isOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
class X {
|
||||
{
|
||||
JButton b = new JButton();
|
||||
b.addActionListener(this::clicked);
|
||||
}
|
||||
|
||||
private void clicked(ActionEvent e) {
|
||||
System.out.println("clicked");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
class X {
|
||||
{
|
||||
JButton b = new JButton();
|
||||
b.addActionListener(e -> {
|
||||
<spot>System.out.println("clicked");</spot>
|
||||
});
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>Intention to extract the lambda body to a method and convert lambda to method reference.</p>
|
||||
</body>
|
||||
</html>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
{
|
||||
I i = i1 -> System.out.prin<caret>tln(i1);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
void m() {}
|
||||
{
|
||||
I i = i1 -> System.out.prin<caret>tln(i1 + "");
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
private static void m(int i1) {
|
||||
System.out.println(i1 + "");
|
||||
}
|
||||
|
||||
void m() {}
|
||||
{
|
||||
I i = B::m;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
interface I<T> {
|
||||
void m(T t);
|
||||
}
|
||||
class B {
|
||||
<N> void n(){
|
||||
I<N> i = i1 -> {
|
||||
System.out.prin<caret>tln(i1);
|
||||
System.out.println(i1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
interface I<T> {
|
||||
void m(T t);
|
||||
}
|
||||
class B {
|
||||
private static <N> void m(N i1) {
|
||||
System.out.println(i1);
|
||||
System.out.println(i1);
|
||||
}
|
||||
|
||||
<N> void n(){
|
||||
I<N> i = B::m
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
void m(int i) {}
|
||||
{
|
||||
I i = i1 -> System.out.prin<caret>tln(i1 + "");
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
private static void m2(int i1) {
|
||||
System.out.println(i1 + "");
|
||||
}
|
||||
|
||||
void m(int i) {}
|
||||
{
|
||||
I i = B::m2;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
class X {
|
||||
I i = i1 -> System.out.prin<caret>tln("");
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
class X {
|
||||
private void m(int i1) {
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
I i = this::m;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
{
|
||||
I i = i1 -> System.out.prin<caret>tln("");
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
private static void m(int i1) {
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
{
|
||||
I i = B::m;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
interface I {
|
||||
void m(int i);
|
||||
}
|
||||
class B {
|
||||
{
|
||||
int a = 0;
|
||||
I i = i1 -> System.out.prin<caret>tln(i1 + a);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.siyeh.ipp.functional;
|
||||
|
||||
import com.siyeh.IntentionPowerPackBundle;
|
||||
import com.siyeh.ipp.IPPTestCase;
|
||||
|
||||
public class ExtractToMethodReferenceTest extends IPPTestCase {
|
||||
|
||||
public void testUnusedParameterToConfirmSignature() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testOverloadedMethodSameName() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testSameSignatureMethodExists() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testUnableToCreateStatic() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testRequiredTypeParameter() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testConvertableToMethodReference() throws Exception {
|
||||
assertIntentionNotAvailable();
|
||||
}
|
||||
|
||||
public void testUsedLocalVariables() throws Exception {
|
||||
assertIntentionNotAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIntentionName() {
|
||||
return IntentionPowerPackBundle.message("extract.to.method.reference.intention.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRelativePath() {
|
||||
return "functional/extractToMethodReference";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user