PY-17265 Introduce new dialog for escalating local functions and methods

This dialog is similar to that previously used to move module members.
This commit is contained in:
Mikhail Golubev
2016-10-25 00:03:49 +03:00
parent 39b7d7fe66
commit f3653451e0
9 changed files with 266 additions and 285 deletions
+1 -8
View File
@@ -465,7 +465,7 @@
<automaticRenamerFactory implementation="com.jetbrains.python.refactoring.rename.PyParametersRenameFactory"/>
<moveFileHandler implementation="com.jetbrains.python.refactoring.move.PyMoveFileHandler"/>
<refactoring.moveHandler implementation="com.jetbrains.python.refactoring.move.PyMoveModuleMembersDelegate"/>
<refactoring.moveHandler implementation="com.jetbrains.python.refactoring.move.PyMoveSymbolDelegate"/>
<refactoring.changeSignatureUsageProcessor implementation="com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureUsageProcessor" id="Python"/>
@@ -830,13 +830,6 @@
<add-to-group group-id="DebugMainMenu" relative-to-action="StepInto" anchor="after"/>
<add-to-group group-id="XDebugger.ToolWindow.TopToolbar" relative-to-action="StepInto" anchor="after"/>
</action>
<action id="PyConvertLocalFunctionToTopLevelFunctionAction" class="com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelRefactoring"
text="Make Top-Level Function"
description="Convert local function or method to top-level function, transforming names from enclosing scope and instance attributes into parameters">
<add-to-group group-id="RefactoringMenu"/>
</action>
</actions>
<extensions defaultExtensionNs="com.intellij.spellchecker">
@@ -662,9 +662,11 @@ refactoring.move.module.members.error.cannot.use.module.name.$0=Cannot use modul
refactoring.move.module.members.error.selection=Cannot perform refactoring using selected element(s)
# Make function top-level
refactoring.make.function.top.level.function=Convert to top-level function
refactoring.make.method.top.level=Make method top-level
refactoring.make.local.function.top.level=Make local function top-level
refactoring.make.method.top.level.dialog.title=Make Method Top-Level
refactoring.make.method.top.level.dialog.description=Create top-level function from the method {0}
refactoring.make.local.function.top.level.dialog.title=Make Local Function Top-Level
refactoring.make.local.function.top.level.dialog.description=Create top-level function from the local function {0}
refactoring.make.function.top.level.error.nonlocal.writes=Cannot move function with nonlocal writes
refactoring.make.function.top.level.error.self.reads=Cannot move function that contains usages of "self" parameter from outer scope
refactoring.make.function.top.level.error.outer.scope.reads=Cannon move method that references names from the outer scope
@@ -18,7 +18,6 @@ package com.jetbrains.python.refactoring.makeFunctionTopLevel;
import com.intellij.codeInsight.controlflow.ControlFlow;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -52,13 +51,13 @@ import static com.jetbrains.python.psi.PyUtil.as;
public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
protected final PyFunction myFunction;
protected final PyResolveContext myResolveContext;
protected final Editor myEditor;
protected final PyElementGenerator myGenerator;
protected final String myDestinationPath;
public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
super(targetFunction.getProject());
myFunction = targetFunction;
myEditor = editor;
myDestinationPath = destinationPath;
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
myGenerator = PyElementGenerator.getInstance(myProject);
@@ -99,10 +98,7 @@ public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactorin
assert ApplicationManager.getApplication().isWriteAccessAllowed();
updateUsages(newParameters, usages);
final PyFunction newFunction = replaceFunction(createNewFunction(newParameters));
myEditor.getSelectionModel().removeSelection();
myEditor.getCaretModel().moveToOffset(newFunction.getTextOffset());
replaceFunction(createNewFunction(newParameters));
}
@NotNull
@@ -0,0 +1,65 @@
/*
* Copyright 2000-2015 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.jetbrains.python.refactoring.makeFunctionTopLevel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.refactoring.move.PyBaseMoveDialog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Mikhail Golubev
*/
public class PyMakeFunctionTopLevelDialog extends PyBaseMoveDialog {
public PyMakeFunctionTopLevelDialog(@NotNull Project project,
@NotNull PyFunction function,
@NotNull String sourcePath,
@NotNull String destinationPath) {
super(project, sourcePath, destinationPath);
String functionName = function.getName();
if (StringUtil.isNotEmpty(functionName)) {
functionName += "()";
}
if (function.getContainingClass() != null) {
setTitle(PyBundle.message("refactoring.make.method.top.level.dialog.title"));
myDescription.setText(PyBundle.message("refactoring.make.method.top.level.dialog.description", functionName));
}
else {
setTitle(PyBundle.message("refactoring.make.local.function.top.level.dialog.title"));
myDescription.setText(PyBundle.message("refactoring.make.local.function.top.level.dialog.description", functionName));
}
if (function.getContainingClass() != null) {
myDescription.setText("Make top-level function from method ");
}
myExtraPanel.setVisible(false);
init();
}
@Override
protected String getHelpId() {
return "python.reference.makeFunctionTopLevel";
}
@Nullable
@Override
protected String getDimensionServiceKey() {
return "#com.jetbrains.python.refactoring.move.PyMakeFunctionTopLevelDialog";
}
}
@@ -1,144 +0,0 @@
/*
* Copyright 2000-2015 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.jetbrains.python.refactoring.makeFunctionTopLevel;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.PyBaseRefactoringAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* @author Mikhail Golubev
*/
public class PyMakeFunctionTopLevelRefactoring extends PyBaseRefactoringAction {
public static final String ID = "py.make.function.top.level";
@Override
protected boolean isAvailableInEditorOnly() {
return true;
}
@Override
protected boolean isEnabledOnElementInsideEditor(@NotNull PsiElement element,
@NotNull Editor editor,
@NotNull PsiFile file,
@NotNull DataContext context) {
return findTargetFunction(element) != null;
}
@Override
protected boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements) {
return false;
}
@Nullable
private static PyFunction findTargetFunction(@NotNull PsiElement element) {
if (isLocalFunction(element) || isSuitableInstanceMethod(element)) {
return (PyFunction)element;
}
// e.g. caret is on "def" keyword
if (isLocalFunction(element.getParent()) || isSuitableInstanceMethod(element.getParent())) {
return (PyFunction)element.getParent();
}
final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
if (refExpr == null) {
return null;
}
final PsiElement resolved = refExpr.getReference().resolve();
if (isLocalFunction(resolved) || isSuitableInstanceMethod(resolved)) {
return (PyFunction)resolved;
}
return null;
}
private static boolean isSuitableInstanceMethod(@Nullable PsiElement element) {
final PyFunction function = as(element, PyFunction.class);
if (function == null || function.getContainingClass() == null) {
return false;
}
final String funcName = function.getName();
if (funcName == null || PyUtil.isSpecialName(funcName)) {
return false;
}
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(function.getProject(), function.getContainingFile());
if (PySuperMethodsSearch.search(function, typeEvalContext).findFirst() != null) return false;
if (PyOverridingMethodsSearch.search(function, true).findFirst() != null) return false;
if (function.getDecoratorList() != null || function.getModifier() != null) return false;
if (function.getContainingClass().findPropertyByCallable(function) != null) return false;
return true;
}
private static boolean isLocalFunction(@Nullable PsiElement resolved) {
return resolved instanceof PyFunction && PsiTreeUtil.getParentOfType(resolved, ScopeOwner.class, true) instanceof PyFunction;
}
@Nullable
@Override
protected RefactoringActionHandler getHandler(@NotNull DataContext dataContext) {
return new RefactoringActionHandler() {
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
if (element != null) {
final PyFunction function = findTargetFunction(element);
if (function != null) {
final PyBaseMakeFunctionTopLevelProcessor processor;
if (isSuitableInstanceMethod(function)) {
processor = new PyMakeMethodTopLevelProcessor(function, editor);
}
else {
processor = new PyMakeLocalFunctionTopLevelProcessor(function, editor);
}
try {
processor.run();
}
catch (IncorrectOperationException e) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw e;
}
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), ID, project);
}
}
}
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
// should be called only from the editor
}
};
}
}
@@ -16,7 +16,6 @@
package com.jetbrains.python.refactoring.makeFunctionTopLevel;
import com.google.common.collect.Lists;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.usageView.UsageInfo;
@@ -41,15 +40,14 @@ import static com.jetbrains.python.psi.PyUtil.as;
*/
public class PyMakeLocalFunctionTopLevelProcessor extends PyBaseMakeFunctionTopLevelProcessor {
protected PyMakeLocalFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
super(targetFunction, editor);
setPreviewUsages(false);
public PyMakeLocalFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destination) {
super(targetFunction, destination);
}
@Override
@NotNull
protected String getRefactoringName() {
return PyBundle.message("refactoring.make.local.function.top.level");
return PyBundle.message("refactoring.make.local.function.top.level.dialog.title");
}
@Override
@@ -17,8 +17,6 @@ package com.jetbrains.python.refactoring.makeFunctionTopLevel;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -53,16 +51,14 @@ public class PyMakeMethodTopLevelProcessor extends PyBaseMakeFunctionTopLevelPro
private final MultiMap<String, PyReferenceExpression> myAttributeReferences = MultiMap.create();
private final Set<PsiElement> myReadsOfSelfParam = new HashSet<>();
public PyMakeMethodTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
super(targetFunction, editor);
// It's easier to debug without preview
setPreviewUsages(!ApplicationManager.getApplication().isInternal());
public PyMakeMethodTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destination) {
super(targetFunction, destination);
}
@NotNull
@Override
protected String getRefactoringName() {
return PyBundle.message("refactoring.make.method.top.level");
return PyBundle.message("refactoring.make.method.top.level.dialog.title");
}
@Override
@@ -17,16 +17,16 @@ package com.jetbrains.python.refactoring.move;
import com.google.common.collect.Lists;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.MoveCallback;
@@ -35,63 +35,95 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelDialog;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeLocalFunctionTopLevelProcessor;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeMethodTopLevelProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* @author vlan
*/
public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
public class PyMoveSymbolDelegate extends MoveHandlerDelegate {
@Override
public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
if (!super.canMove(elements, targetContainer)) {
return false;
}
// Local function or method
if (findTargetFunction(elements[0]) != null) {
return true;
}
// Top-level module member
for (PsiElement element : elements) {
if (!PyMoveModuleMembersHelper.isMovableModuleMember(element)) {
return false;
}
}
return super.canMove(elements, targetContainer);
return true;
}
@Override
public void doMove(Project project,
PsiElement[] elements,
@Nullable PsiElement targetContainer,
@Nullable MoveCallback callback) {
final List<PsiNamedElement> initialElements = Lists.newArrayList();
for (PsiElement element : elements) {
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
if (e == null) {
return;
}
initialElements.add(e);
}
public void doMove(Project project, PsiElement[] elements, @Nullable PsiElement targetContainer, @Nullable MoveCallback callback) {
String initialPath = null;
if (targetContainer instanceof PsiFile) {
final VirtualFile virtualFile = ((PsiFile)targetContainer).getVirtualFile();
if (virtualFile != null) {
initialPath = FileUtil.toSystemDependentName(virtualFile.getPath());
}
initialPath = StringUtil.notNullize(PyPsiUtils.getContainingFilePath(targetContainer));
}
if (initialPath == null) {
initialPath = StringUtil.notNullize(PyPsiUtils.getContainingFilePath(elements[0]));
}
final PyMoveModuleMembersDialog dialog = new PyMoveModuleMembersDialog(project, initialElements, initialPath, initialPath);
if (!dialog.showAndGet()) {
return;
final BaseRefactoringProcessor processor;
final PyFunction function = findTargetFunction(elements[0]);
if (function != null) {
final PyMakeFunctionTopLevelDialog dialog = new PyMakeFunctionTopLevelDialog(project, function, initialPath, initialPath);
if (!dialog.showAndGet()) {
return;
}
if (function.getContainingClass() != null) {
processor = new PyMakeMethodTopLevelProcessor(function, dialog.getTargetPath());
}
else {
processor = new PyMakeLocalFunctionTopLevelProcessor(function, dialog.getTargetPath());
}
processor.setPreviewUsages(dialog.isPreviewUsages());
}
final String destination = dialog.getTargetPath();
final boolean previewUsages = dialog.isPreviewUsages();
try {
else {
final List<PsiNamedElement> initialElements = Lists.newArrayList();
for (PsiElement element : elements) {
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
if (e == null) {
return;
}
initialElements.add(e);
}
final PyMoveModuleMembersDialog dialog = new PyMoveModuleMembersDialog(project, initialElements, initialPath, initialPath);
if (!dialog.showAndGet()) {
return;
}
final boolean previewUsages = dialog.isPreviewUsages();
final PsiNamedElement[] selectedElements = ContainerUtil.findAllAsArray(dialog.getSelectedTopLevelSymbols(), PsiNamedElement.class);
final BaseRefactoringProcessor processor = new PyMoveModuleMembersProcessor(project, selectedElements, destination, previewUsages);
processor = new PyMoveModuleMembersProcessor(project, selectedElements, dialog.getTargetPath(), previewUsages);
}
try {
processor.run();
}
catch (IncorrectOperationException e) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw e;
}
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, project);
}
}
@@ -121,7 +153,7 @@ public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
// Fallback to the old way to select single element to move
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
if (e != null && PyMoveModuleMembersHelper.hasMovableElementType(e)) {
if (PyMoveModuleMembersHelper.isMovableModuleMember(e)) {
if (PyMoveModuleMembersHelper.isMovableModuleMember(e) || findTargetFunction(e) != null) {
doMove(project, new PsiElement[]{e}, targetContainer, null);
}
else {
@@ -154,4 +186,45 @@ public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
return body != null && selectionRange.contains(body.getTextRange());
});
}
@Nullable
public static PyFunction findTargetFunction(@NotNull PsiElement element) {
if (isLocalFunction(element) || isSuitableInstanceMethod(element)) {
return (PyFunction)element;
}
// e.g. caret is on "def" keyword
if (isLocalFunction(element.getParent()) || isSuitableInstanceMethod(element.getParent())) {
return (PyFunction)element.getParent();
}
final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
if (refExpr == null) {
return null;
}
final PsiElement resolved = refExpr.getReference().resolve();
if (isLocalFunction(resolved) || isSuitableInstanceMethod(resolved)) {
return (PyFunction)resolved;
}
return null;
}
public static boolean isSuitableInstanceMethod(@Nullable PsiElement element) {
final PyFunction function = as(element, PyFunction.class);
if (function == null || function.getContainingClass() == null) {
return false;
}
final String funcName = function.getName();
if (funcName == null || PyUtil.isSpecialName(funcName)) {
return false;
}
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(function.getProject(), function.getContainingFile());
if (PySuperMethodsSearch.search(function, typeEvalContext).findFirst() != null) return false;
if (PyOverridingMethodsSearch.search(function, true).findFirst() != null) return false;
if (function.getDecoratorList() != null || function.getModifier() != null) return false;
if (function.getContainingClass().findPropertyByCallable(function) != null) return false;
return true;
}
private static boolean isLocalFunction(@Nullable PsiElement resolved) {
return resolved instanceof PyFunction && PsiTreeUtil.getParentOfType(resolved, ScopeOwner.class, true) instanceof PyFunction;
}
}
@@ -15,71 +15,73 @@
*/
package com.jetbrains.python.refactoring;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.TestActionEvent;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelRefactoring;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeLocalFunctionTopLevelProcessor;
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeMethodTopLevelProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
/**
* @author Mikhail Golubev
*/
public class PyMakeFunctionTopLevelTest extends PyTestCase {
public void doTest(boolean enabled, @Nullable String message) {
public void doTest(@Nullable String message) {
myFixture.configureByFile(getTestName(true) + ".py");
final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
// Similar to com.intellij.testFramework.fixtures.CodeInsightTestFixture.testAction()
final TestActionEvent event = new TestActionEvent(action);
action.beforeActionPerformedUpdate(event);
assertEquals(enabled, event.getPresentation().isEnabledAndVisible());
if (enabled) {
try {
action.actionPerformed(event);
myFixture.checkResultByFile(getTestName(true) + ".after.py");
}
catch (IncorrectOperationException e) {
if (message == null) {
fail("Refactoring failed unexpectedly with message: " + e.getMessage());
final PyFunction function = assertInstanceOf(myFixture.getElementAtCaret(), PyFunction.class);
final String destination = PyPsiUtils.getContainingFilePath(function);
assertNotNull(destination);
try {
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), new Runnable() {
@Override
public void run() {
if (function.getContainingClass() != null) {
new PyMakeMethodTopLevelProcessor(function, destination).run();
}
else {
new PyMakeLocalFunctionTopLevelProcessor(function, destination).run();
}
}
assertEquals(message, e.getMessage());
});
myFixture.checkResultByFile(getTestName(true) + ".after.py");
}
catch (IncorrectOperationException e) {
if (message == null) {
fail("Refactoring failed unexpectedly with message: " + e.getMessage());
}
assertEquals(message, e.getMessage());
}
}
private void doMultiFileTest() throws IOException {
final String rootBeforePath = getTestName(true) + "/before";
final String rootAfterPath = getTestName(true) + "/after";
final VirtualFile copiedDirectory = myFixture.copyDirectoryToProject(rootBeforePath, "");
myFixture.configureByFile("main.py");
myFixture.testAction(new PyMakeFunctionTopLevelRefactoring());
PlatformTestUtil.assertDirectoriesEqual(getVirtualFileByName(getTestDataPath() + rootAfterPath), copiedDirectory);
}
//private void doMultiFileTest() throws IOException {
// final String rootBeforePath = getTestName(true) + "/before";
// final String rootAfterPath = getTestName(true) + "/after";
// final VirtualFile copiedDirectory = myFixture.copyDirectoryToProject(rootBeforePath, "");
// myFixture.configureByFile("main.py");
// myFixture.testAction(new PyMakeFunctionTopLevelRefactoring());
// PlatformTestUtil.assertDirectoriesEqual(getVirtualFileByName(getTestDataPath() + rootAfterPath), copiedDirectory);
//}
private void doTestSuccess() {
doTest(true, null);
doTest(null);
}
private void doTestFailure(@NotNull String message) {
doTest(true, message);
doTest(message);
}
private static boolean isActionEnabled() {
final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
final TestActionEvent event = new TestActionEvent(action);
action.beforeActionPerformedUpdate(event);
return event.getPresentation().isEnabled();
}
//private static boolean isActionEnabled() {
// final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
// final TestActionEvent event = new TestActionEvent(action);
// action.beforeActionPerformedUpdate(event);
// return event.getPresentation().isEnabled();
//}
// PY-6637
public void testLocalFunctionSimple() {
@@ -87,43 +89,43 @@ public class PyMakeFunctionTopLevelTest extends PyTestCase {
}
// PY-6637
public void testRefactoringAvailability() {
myFixture.configureByFile(getTestName(true) + ".py");
final PsiFile file = myFixture.getFile();
moveByText("func");
assertFalse(isActionEnabled());
moveByText("local");
assertTrue(isActionEnabled());
// move to "def" keyword
myFixture.getEditor().getCaretModel().moveCaretRelatively(-3, 0, false, false, false);
final PsiElement tokenAtCaret = file.findElementAt(myFixture.getCaretOffset());
assertNotNull(tokenAtCaret);
assertEquals(tokenAtCaret.getNode().getElementType(), PyTokenTypes.DEF_KEYWORD);
assertTrue(isActionEnabled());
moveByText("method");
assertTrue(isActionEnabled());
moveByText("static_method");
assertFalse(isActionEnabled());
moveByText("class_method");
assertFalse(isActionEnabled());
// Overridden method
moveByText("overridden_method");
assertFalse(isActionEnabled());
// Overriding method
moveByText("upper");
assertFalse(isActionEnabled());
moveByText("property");
assertFalse(isActionEnabled());
moveByText("__magic__");
assertFalse(isActionEnabled());
}
//public void testRefactoringAvailability() {
// myFixture.configureByFile(getTestName(true) + ".py");
//
// final PsiFile file = myFixture.getFile();
// moveByText("func");
// assertFalse(isActionEnabled());
// moveByText("local");
// assertTrue(isActionEnabled());
//
// // move to "def" keyword
// myFixture.getEditor().getCaretModel().moveCaretRelatively(-3, 0, false, false, false);
// final PsiElement tokenAtCaret = file.findElementAt(myFixture.getCaretOffset());
// assertNotNull(tokenAtCaret);
// assertEquals(tokenAtCaret.getNode().getElementType(), PyTokenTypes.DEF_KEYWORD);
// assertTrue(isActionEnabled());
//
// moveByText("method");
// assertTrue(isActionEnabled());
//
// moveByText("static_method");
// assertFalse(isActionEnabled());
// moveByText("class_method");
// assertFalse(isActionEnabled());
//
// // Overridden method
// moveByText("overridden_method");
// assertFalse(isActionEnabled());
//
// // Overriding method
// moveByText("upper");
// assertFalse(isActionEnabled());
//
// moveByText("property");
// assertFalse(isActionEnabled());
// moveByText("__magic__");
// assertFalse(isActionEnabled());
//}
// PY-6637
public void testLocalFunctionNonlocalReferenceToOuterScope() {
@@ -182,9 +184,9 @@ public class PyMakeFunctionTopLevelTest extends PyTestCase {
doTestSuccess();
}
public void testMethodImportUpdates() throws IOException {
doMultiFileTest();
}
//public void testMethodImportUpdates() throws IOException {
// doMultiFileTest();
//}
public void testMethodCalledViaClass() {
doTestSuccess();