PY-21398 Wrap batch updates of existing usages in quickfixes with PotemkinProgress

It also fixes EA-425763 about using incorrect parent and anchor elements
to insert a function generated from a method of a nested class.

GitOrigin-RevId: f9b9da303396eb78c2c12b2750065dbdc0ba70ed
This commit is contained in:
Mikhail Golubev
2021-10-05 20:36:57 +03:00
committed by intellij-monorepo-bot
parent 71a7d544c6
commit 3b6dea06a3
6 changed files with 92 additions and 29 deletions

View File

@@ -69,6 +69,8 @@ refactoring.inline.function.nonlocal=Cannot inline functions with nonlocal varia
refactoring.inline.function.nested=Cannot inline functions with another function declaration
refactoring.inline.function.interrupts.flow=Cannot inline functions that interrupt control flow
refactoring.progress.title.updating.existing.usages=Updating existing usages...
### Annotators ###
ANN.deleting.none=Deleting None
ANN.assign.to.none=Assignment to None

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.inspections.quickfix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -20,7 +21,9 @@ import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferenc
import com.jetbrains.python.inspections.unresolvedReference.SimplePyUnresolvedReferencesInspection;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.TypeEvalContext;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -42,27 +45,36 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix {
final PyClass containingClass = problemFunction.getContainingClass();
if (containingClass == null) return;
final List<UsageInfo> usages = PyPsiIndexUtil.findUsages(problemFunction, false);
final PyParameter[] parameters = problemFunction.getParameterList().getParameters();
final List<PyReferenceExpression> usages = StreamEx.of(PyPsiIndexUtil.findUsages(problemFunction, false))
.map(UsageInfo::getElement)
.select(PyReferenceExpression.class)
.toList();
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(
PyPsiBundle.message("refactoring.progress.title.updating.existing.usages"), problemFunction.getProject(), null, (indicator -> {
PyFunction function = transformDefinition(problemFunction);
for (int i = 0; i < usages.size(); i++) {
indicator.checkCanceled();
indicator.setFraction((i + 1.0) / usages.size());
PyReferenceExpression usage = usages.get(i);
PsiFile usageFile = usage.getContainingFile();
updateUsage(function, usage, usageFile, !usageFile.equals(containingClass.getContainingFile()));
}
})
);
}
@NotNull
private static PyFunction transformDefinition(@NotNull PyFunction method) {
PyParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length > 0) {
parameters[0].delete();
}
PsiElement copy = problemFunction.copy();
problemFunction.delete();
final PsiElement parent = containingClass.getParent();
PyClass aClass = PsiTreeUtil.getTopmostParentOfType(containingClass, PyClass.class);
if (aClass == null)
aClass = containingClass;
copy = parent.addBefore(copy, aClass);
for (UsageInfo usage : usages) {
final PsiElement usageElement = usage.getElement();
if (usageElement instanceof PyReferenceExpression) {
final PsiFile usageFile = usageElement.getContainingFile();
updateUsage(copy, (PyReferenceExpression)usageElement, usageFile, !usageFile.equals(parent));
}
}
PyClass topmostClass = PsiTreeUtil.getTopmostParentOfType(method, PyClass.class);
assert topmostClass != null;
PsiElement copy = method.copy();
method.delete();
return (PyFunction)topmostClass.getParent().addBefore(copy, topmostClass);
}
private static void updateUsage(@NotNull final PsiElement finalElement, @NotNull final PyReferenceExpression element,
@@ -144,4 +156,14 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix {
arguments[0].delete();
}
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public @Nullable PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
return currentFile;
}
}

View File

@@ -3,8 +3,10 @@ package com.jetbrains.python.inspections.quickfix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.usageView.UsageInfo;
@@ -12,7 +14,9 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.codeInsight.PyPsiIndexUtil;
import com.jetbrains.python.psi.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -31,21 +35,30 @@ public class PyMakeMethodStaticQuickFix implements LocalQuickFix {
final PsiElement element = descriptor.getPsiElement();
final PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class);
if (problemFunction == null) return;
final List<UsageInfo> usages = PyPsiIndexUtil.findUsages(problemFunction, false);
final PyParameter[] parameters = problemFunction.getParameterList().getParameters();
List<PyReferenceExpression> usages = StreamEx.of(PyPsiIndexUtil.findUsages(problemFunction, false))
.map(UsageInfo::getElement)
.select(PyReferenceExpression.class)
.toList();
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(
PyPsiBundle.message("refactoring.progress.title.updating.existing.usages"), problemFunction.getProject(), null, (indicator -> {
updateDefinition(problemFunction);
for (int i = 0; i < usages.size(); i++) {
indicator.checkCanceled();
indicator.setFraction((i + 1.0) / usages.size());
updateUsage(usages.get(i));
}
})
);
}
private static void updateDefinition(@NotNull PyFunction function) {
final PyParameter[] parameters = function.getParameterList().getParameters();
if (parameters.length > 0) {
parameters[0].delete();
}
PyUtil.addDecorator(problemFunction, "@" + PyNames.STATICMETHOD);
for (UsageInfo usage : usages) {
final PsiElement usageElement = usage.getElement();
if (usageElement instanceof PyReferenceExpression) {
updateUsage((PyReferenceExpression)usageElement);
}
}
PyUtil.addDecorator(function, "@" + PyNames.STATICMETHOD);
}
private static void updateUsage(@NotNull final PyReferenceExpression element) {
@@ -69,4 +82,14 @@ public class PyMakeMethodStaticQuickFix implements LocalQuickFix {
arguments[0].delete();
}
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public @Nullable PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
return currentFile;
}
}

View File

@@ -0,0 +1,4 @@
class Outer:
class Inner:
def met<caret>hod(self):
return 42

View File

@@ -0,0 +1,7 @@
def method():
return 42
class Outer:
class Inner:
pass

View File

@@ -80,4 +80,9 @@ public class PyMakeFunctionFromMethodQuickFixTest extends PyQuickFixTestCase {
public void testRemoveQualifiers() {
doQuickFixTest(PyMethodMayBeStaticInspection.class, PyPsiBundle.message("QFIX.NAME.make.function"));
}
// EA-425763
public void testInnerClassMethod() {
doQuickFixTest(PyMethodMayBeStaticInspection.class, PyPsiBundle.message("QFIX.NAME.make.function"));
}
}