Merge branch 'east825/py-move-to-toplevel'
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 8 Oct 2015 09:02:11 +0000 (12:02 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 8 Oct 2015 09:02:11 +0000 (12:02 +0300)
63 files changed:
python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java
python/src/META-INF/python-core.xml
python/src/com/jetbrains/python/PyBundle.properties
python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java
python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java
python/src/com/jetbrains/python/inspections/quickfix/AddCallSuperQuickFix.java
python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java
python/src/com/jetbrains/python/inspections/quickfix/PyMakeMethodStaticQuickFix.java
python/src/com/jetbrains/python/psi/PyUtil.java
python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java
python/src/com/jetbrains/python/refactoring/PyBaseRefactoringAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/refactoring/PyRefactoringUtil.java
python/src/com/jetbrains/python/refactoring/convertModulePackage/PyBaseConvertModulePackageAction.java [moved from python/src/com/jetbrains/python/refactoring/convert/PyBaseConvertRefactoringAction.java with 61% similarity]
python/src/com/jetbrains/python/refactoring/convertModulePackage/PyConvertModuleToPackageAction.java [moved from python/src/com/jetbrains/python/refactoring/convert/PyConvertModuleToPackageAction.java with 93% similarity]
python/src/com/jetbrains/python/refactoring/convertModulePackage/PyConvertPackageToModuleAction.java [moved from python/src/com/jetbrains/python/refactoring/convert/PyConvertPackageToModuleAction.java with 96% similarity]
python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java
python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyBaseMakeFunctionTopLevelProcessor.java [new file with mode: 0644]
python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeFunctionTopLevelRefactoring.java [new file with mode: 0644]
python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeLocalFunctionTopLevelProcessor.java [new file with mode: 0644]
python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeMethodTopLevelProcessor.java [new file with mode: 0644]
python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers.py [new file with mode: 0644]
python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers_after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferenceToOuterScope.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionReferenceToSelf.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodAttributeWrites.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/main.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/other.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/main.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/other.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodNonlocalReferenceToOuterScope.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodOtherMethodCalls.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodOuterScopeReads.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodOverriddenSelf.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodReadPrivateAttributes.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodSelfUsedAsOperand.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.after.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.py [new file with mode: 0644]
python/testData/refactoring/makeFunctionTopLevel/refactoringAvailability.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/quickFixes/PyMakeFunctionFromMethodQuickFixTest.java
python/testSrc/com/jetbrains/python/refactoring/PyConvertModuleToPackageTest.java
python/testSrc/com/jetbrains/python/refactoring/PyConvertPackageToModuleTest.java
python/testSrc/com/jetbrains/python/refactoring/PyMakeFunctionTopLevelTest.java [new file with mode: 0644]

index 09dc5cee52321a9a89fd80ae50578c00ed479b5e..11a3dd090178bc3ddf0519b460d14eadd721e342 100644 (file)
@@ -121,6 +121,18 @@ public abstract class PyElementGenerator {
 
   public abstract PyNamedParameter createParameter(@NotNull String name);
 
+  /**
+   * @param text parameters list already in parentheses, e.g. {@code (foo, *args, **kwargs)}.
+   */
+  @NotNull
+  public abstract PyParameterList createParameterList(@NotNull LanguageLevel languageLevel, @NotNull String text);
+
+  /**
+   * @param text argument list already in parentheses, e.g. {@code (1, 2, *xs)}.
+   */
+  @NotNull
+  public abstract PyArgumentList createArgumentList(@NotNull LanguageLevel languageLevel, @NotNull String text);
+
   public abstract PyKeywordArgument createKeywordArgument(LanguageLevel languageLevel, String keyword, String value);
 
   public abstract PsiFile createDummyFile(LanguageLevel langLevel, String contents);
index dedf9b380cfa6b42bd2ec8cb18a69bf7619b7295..3883fd9064b61527d8b494b3f50b1d5aaa7147b9 100644 (file)
       <className>com.jetbrains.python.codeInsight.intentions.PyConvertStaticMethodToFunctionIntention</className>
       <category>Python</category>
     </intentionAction>
-
+    
     <intentionAction>
       <className>com.jetbrains.python.codeInsight.intentions.SpecifyTypeInDocstringIntention</className>
       <category>Python</category>
       <add-to-group group-id="XDebugger.ValueGroup" anchor="after" relative-to-action="Debugger.Tree.AddToWatches"/>
     </action>
 
-    <action id="PyConvertModuleToPackage" class="com.jetbrains.python.refactoring.convert.PyConvertModuleToPackageAction"
+    <action id="PyConvertModuleToPackage" class="com.jetbrains.python.refactoring.convertModulePackage.PyConvertModuleToPackageAction"
             text="Convert to Python Package"
             description="Create package with the same name and move content of the module to its __init__.py">
       <add-to-group group-id="RefactoringMenu" anchor="last" />
     </action>
 
-    <action id="PyConvertPackageToModuleAction" class="com.jetbrains.python.refactoring.convert.PyConvertPackageToModuleAction"
+    <action id="PyConvertPackageToModuleAction" class="com.jetbrains.python.refactoring.convertModulePackage.PyConvertPackageToModuleAction"
             text="Convert to Python Module"
             description="Create module with the same name and move content of __init__.py to that module">
       <add-to-group group-id="RefactoringMenu" anchor="last" />
       <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">
index bca9672a2650647cca54e5c2523c86985f0f2a9f..efd29f0996bf5a1acf18da28ae2d718b976c7940 100644 (file)
@@ -1,4 +1,4 @@
-f### Generic words ###
+f###=Generic words ###
 GNAME.function=function
 GNAME.class=class
 GNAME.var=variable
@@ -632,6 +632,20 @@ refactoring.move.module.members.error.destination.file.contains.global.variable.
 refactoring.move.module.members.error.cannot.use.module.name.$0=Cannot use module name ''{0}'' in imports
 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.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
+refactoring.make.function.top.level.error.private.attributes=Cannot move method that references private instance attributes
+refactoring.make.function.top.level.error.attribute.writes=Cannot move method that writes to instance attributes
+refactoring.make.function.top.level.error.method.calls=Cannot move method that calls other methods of the same class
+refactoring.make.function.top.level.error.special.usage.of.self=Cannot move method that contains special usages of "self" parameter
+
+
+
 #change signature
 refactoring.change.signature.usage.view.declarations.header=Functions to be refactored
 refactoring.change.signature.dialog.validation.name.defined=Name is already defined in scope
index eb1f6a0f05e818a965f2d28f5bd44b1ddc0ad80a..25ca14531983100787e1d85f29a7a81aaa9b4939 100644 (file)
@@ -277,6 +277,7 @@ public class PyCodeFragmentUtil {
           for (PsiElement resolved : multiResolve(reference)) {
             if (!subGraphElements.contains(resolved)) {
               result.add(element);
+              break;
             }
           }
         }
index 45f8fb9b3deca9207adc0a42aea21ae6acb923c6..4106221a51149cfa60c0c33896cc7ebba823537a 100644 (file)
@@ -184,7 +184,7 @@ public class ImportToImportFromIntention implements IntentionAction {
             // cut the module out of import, add a from-import.
             for (PyImportElement pie : importElements) {
               if (pie == myImportElement) {
-                PyUtil.removeListNode(pie);
+                pie.delete();
                 break;
               }
             }
index 83617ed4479b57bc83fc9680cadbf6c3c9a37c55..9e88cb75cd24bd98d1bf057bbb0398e2b3a5beeb 100644 (file)
@@ -86,16 +86,11 @@ public class AddCallSuperQuickFix implements LocalQuickFix {
       addSelfToCall = true;
       superCall.append(superClass.getName()).append(".__init__(");
     }
-    final StringBuilder newFunction = new StringBuilder("def __init__(");
 
     final Couple<List<String>> couple = buildNewFunctionParamsAndSuperInitCallArgs(origInfo, superInfo, addSelfToCall);
-    StringUtil.join(couple.getFirst(), ", ", newFunction);
-    newFunction.append(")");
-
-    if (problemFunction.getAnnotation() != null) {
-      newFunction.append(problemFunction.getAnnotation().getText());
-    }
-    newFunction.append(": pass");
+    final StringBuilder newParameters = new StringBuilder("(");
+    StringUtil.join(couple.getFirst(), ", ", newParameters);
+    newParameters.append(")");
 
     StringUtil.join(couple.getSecond(), ", ", superCall);
     superCall.append(")");
@@ -103,10 +98,7 @@ public class AddCallSuperQuickFix implements LocalQuickFix {
     final PyElementGenerator generator = PyElementGenerator.getInstance(project);
     final LanguageLevel languageLevel = LanguageLevel.forElement(problemFunction);
     final PyStatement callSuperStatement = generator.createFromText(languageLevel, PyStatement.class, superCall.toString());
-    final PyParameterList newParameterList = generator.createFromText(languageLevel,
-                                                                      PyParameterList.class,
-                                                                      newFunction.toString(),
-                                                                      new int[]{0, 3});
+    final PyParameterList newParameterList = generator.createParameterList(languageLevel, newParameters.toString());
     problemFunction.getParameterList().replace(newParameterList);
     final PyStatementList statementList = problemFunction.getStatementList();
     PyUtil.addElementToStatementList(callSuperStatement, statementList, true);
index 1024151f34a91b12ee8cbc1a2e4d458658252881..edbf4694251c69034761a5be28846c64458c7691 100644 (file)
@@ -63,7 +63,10 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix {
     if (containingClass == null) return;
 
     final List<UsageInfo> usages = PyRefactoringUtil.findUsages(problemFunction, false);
-    PyUtil.deleteParameter(problemFunction, 0);
+    final PyParameter[] parameters = problemFunction.getParameterList().getParameters();
+    if (parameters.length > 0) {
+      parameters[0].delete();
+    }
 
     PsiElement copy = problemFunction.copy();
     problemFunction.delete();
@@ -103,9 +106,7 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix {
         updateAssignment(element, resolved);
       }
       else if (resolved instanceof PyClass) {     //call with first instance argument A.m(A())
-        final PsiElement dot = qualifier.getNextSibling();
-        if (dot != null) dot.delete();
-        qualifier.delete();
+        PyUtil.removeQualifier(element);
         updateArgumentList(element);
       }
     }
@@ -151,13 +152,11 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix {
   private static void updateArgumentList(@NotNull final PyReferenceExpression element) {
     final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
     if (callExpression == null) return;
-    PyArgumentList argumentList = callExpression.getArgumentList();
+    final PyArgumentList argumentList = callExpression.getArgumentList();
     if (argumentList == null) return;
     final PyExpression[] arguments = argumentList.getArguments();
     if (arguments.length > 0) {
-      final PyExpression argument = arguments[0];
-      PyUtil.eraseWhitespaceAndComma(argument.getParent().getNode(), argument, false);
-      argument.delete();
+      arguments[0].delete();
     }
   }
 }
index dd581340a494767f945da0831c9e06a7c21e4ede..fff3c0165c1bc7e04f10a87400aba6066420fa82 100644 (file)
@@ -55,7 +55,10 @@ public class PyMakeMethodStaticQuickFix implements LocalQuickFix {
     if (problemFunction == null) return;
     final List<UsageInfo> usages = PyRefactoringUtil.findUsages(problemFunction, false);
 
-    PyUtil.deleteParameter(problemFunction, 0);
+    final PyParameter[] parameters = problemFunction.getParameterList().getParameters();
+    if (parameters.length > 0) {
+      parameters[0].delete();
+    }
     final PyDecoratorList problemDecoratorList = problemFunction.getDecoratorList();
     List<String> decoTexts = new ArrayList<String>();
     decoTexts.add("@staticmethod");
@@ -98,13 +101,11 @@ public class PyMakeMethodStaticQuickFix implements LocalQuickFix {
   private static void updateArgumentList(@NotNull final PyReferenceExpression element) {
     final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
     if (callExpression == null) return;
-    PyArgumentList argumentList = callExpression.getArgumentList();
+    final PyArgumentList argumentList = callExpression.getArgumentList();
     if (argumentList == null) return;
     final PyExpression[] arguments = argumentList.getArguments();
     if (arguments.length > 0) {
-      final PyExpression argument = arguments[0];
-      PyUtil.eraseWhitespaceAndComma(argument.getParent().getNode(), argument, false);
-      argument.delete();
+      arguments[0].delete();
     }
   }
 }
index 87d9be86fe81638433e6aa4df0c9b40951d71aea..883e63fac11b550e85911e3e7f7666e2de9e356a 100644 (file)
@@ -295,64 +295,6 @@ public class PyUtil {
     if (addWhitespace) node.addChild(ASTFactory.whitespace(" "), beforeThis);
   }
 
-  /**
-   * Removes an element from a a comma-separated list in a PSI tree. E.g. can turn "foo, bar, baz" into "foo, baz",
-   * removing commas as needed. It removes a trailing comma if it results from deletion.
-   *
-   * @param item what to remove. Its parent is considered the list, and commas must be its peers.
-   */
-  public static void removeListNode(PsiElement item) {
-    PsiElement parent = item.getParent();
-    if (!FileModificationService.getInstance().preparePsiElementForWrite(parent)) {
-      return;
-    }
-    // remove comma after the item
-    ASTNode binder = parent.getNode();
-    assert binder != null : "parent node is null, ensureWritable() lied";
-    boolean got_comma_after = eraseWhitespaceAndComma(binder, item, false);
-    if (!got_comma_after) {
-      // there was not a comma after the item; remove a comma before the item
-      eraseWhitespaceAndComma(binder, item, true);
-    }
-    // finally
-    item.delete();
-  }
-
-  /**
-   * Removes whitespace and comma(s) that are siblings of the item, up to the first non-whitespace and non-comma.
-   *
-   * @param parent_node node of the parent of item.
-   * @param item        starting point; we erase left or right of it, but not it.
-   * @param backwards   true to erase prev siblings, false to erase next siblings.
-   * @return true       if a comma was found and removed.
-   */
-  public static boolean eraseWhitespaceAndComma(ASTNode parent_node, PsiElement item, boolean backwards) {
-    // we operate on AST, PSI won't let us delete whitespace easily.
-    boolean is_comma;
-    boolean got_comma = false;
-    ASTNode current = item.getNode();
-    ASTNode candidate;
-    boolean have_skipped_the_item = false;
-    while (current != null) {
-      candidate = current;
-      current = backwards ? current.getTreePrev() : current.getTreeNext();
-      if (have_skipped_the_item) {
-        is_comma = ",".equals(candidate.getText());
-        got_comma |= is_comma;
-        if (is_comma || candidate.getElementType() == TokenType.WHITE_SPACE) {
-          parent_node.removeChild(candidate);
-        }
-        else {
-          break;
-        }
-      }
-      else {
-        have_skipped_the_item = true;
-      }
-    }
-    return got_comma;
-  }
-
   /**
    * Collects superclasses of a class all the way up the inheritance chain. The order is <i>not</i> necessarily the MRO.
    */
@@ -599,41 +541,23 @@ public class PyUtil {
     return AccessDirection.READ;
   }
 
-  public static boolean deleteParameter(@NotNull final PyFunction problemFunction, int index) {
-    final PyParameterList parameterList = problemFunction.getParameterList();
-    final PyParameter[] parameters = parameterList.getParameters();
-    if (parameters.length <= 0) return false;
-
-    PsiElement first = parameters[index];
-    PsiElement last = parameters.length > index + 1 ? parameters[index + 1] : parameterList.getLastChild();
-    PsiElement prevSibling = last.getPrevSibling() != null ? last.getPrevSibling() : parameters[index];
-
-    parameterList.deleteChildRange(first, prevSibling);
-    return true;
-  }
-
   public static void removeQualifier(@NotNull final PyReferenceExpression element) {
     final PyExpression qualifier = element.getQualifier();
     if (qualifier == null) return;
 
     if (qualifier instanceof PyCallExpression) {
-      final StringBuilder newElement = new StringBuilder(element.getLastChild().getText());
       final PyExpression callee = ((PyCallExpression)qualifier).getCallee();
       if (callee instanceof PyReferenceExpression) {
         final PyExpression calleeQualifier = ((PyReferenceExpression)callee).getQualifier();
         if (calleeQualifier != null) {
-          newElement.insert(0, calleeQualifier.getText() + ".");
+          qualifier.replace(calleeQualifier);
+          return;
         }
       }
-      final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject());
-      final PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), newElement.toString());
-      element.replace(expression);
-    }
-    else {
-      final PsiElement dot = qualifier.getNextSibling();
-      if (dot != null) dot.delete();
-      qualifier.delete();
     }
+    final PsiElement dot = PyPsiUtils.getNextNonWhitespaceSibling(qualifier);
+    if (dot != null) dot.delete();
+    qualifier.delete();
   }
 
   /**
@@ -1099,7 +1023,7 @@ public class PyUtil {
    * @param name
    * @return true iff the name looks like a class-private one, starting with two underscores but not ending with two underscores.
    */
-  public static boolean isClassPrivateName(String name) {
+  public static boolean isClassPrivateName(@NotNull String name) {
     return name.startsWith("__") && !name.endsWith("__");
   }
 
@@ -1264,10 +1188,10 @@ public class PyUtil {
 
   public static class MethodFlags {
 
-    private boolean myIsStaticMethod;
-    private boolean myIsMetaclassMethod;
-    private boolean myIsSpecialMetaclassMethod;
-    private boolean myIsClassMethod;
+    private final boolean myIsStaticMethod;
+    private final boolean myIsMetaclassMethod;
+    private final boolean myIsSpecialMetaclassMethod;
+    private final boolean myIsClassMethod;
 
     /**
      * @return true iff the method belongs to a metaclass (an ancestor of 'type').
index 0b40a3dae4423b0f9a4bc65cb7973072f53bce0f..9ee7076ab4cca6789a6898a3b9fe542846864dd3 100644 (file)
@@ -333,6 +333,19 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
     return createParameter(name, null, null, LanguageLevel.getDefault());
   }
 
+  @NotNull
+  @Override
+  public PyParameterList createParameterList(@NotNull LanguageLevel languageLevel, @NotNull String text) {
+    return createFromText(languageLevel, PyParameterList.class, "def f" + text + ": pass", new int[]{0, 3});
+  }
+
+  @NotNull
+  @Override
+  public PyArgumentList createArgumentList(@NotNull LanguageLevel languageLevel, @NotNull String text) {
+    return createFromText(languageLevel, PyArgumentList.class, "f" + text, new int[]{0, 0, 1});
+  }
+
+
   public PyNamedParameter createParameter(@NotNull String name, @Nullable String defaultValue, @Nullable String annotation,
                                           @NotNull LanguageLevel languageLevel) {
     String parameterText = name;
index 6a3fc84073f57da164a7f8a4a9f3198091bdfd51..8dd70935683b36754e123d4930b67442d5227466 100644 (file)
@@ -31,14 +31,16 @@ import java.util.Set;
  * @author yole
  */
 public class PySuperMethodsSearchExecutor implements QueryExecutor<PsiElement, PySuperMethodsSearch.SearchParameters> {
+  @Override
   public boolean execute(@NotNull final PySuperMethodsSearch.SearchParameters queryParameters,
                          @NotNull final Processor<PsiElement> consumer) {
-    PyFunction func = queryParameters.getDerivedMethod();
-    String name = func.getName();
-    PyClass containingClass = func.getContainingClass();
-    Set<PyClass> foundMethodContainingClasses = new HashSet<PyClass>();
+    final PyFunction func = queryParameters.getDerivedMethod();
+    final String name = func.getName();
+    final PyClass containingClass = func.getContainingClass();
+    final Set<PyClass> foundMethodContainingClasses = new HashSet<PyClass>();
+    final TypeEvalContext context = queryParameters.getContext();
     if (name != null && containingClass != null) {
-      for (PyClass superClass : containingClass.getAncestorClasses(null)) {
+      for (PyClass superClass : containingClass.getAncestorClasses(context)) {
         if (!queryParameters.isDeepSearch()) {
           boolean isAlreadyFound = false;
           for (PyClass alreadyFound : foundMethodContainingClasses) {
@@ -62,14 +64,13 @@ public class PySuperMethodsSearchExecutor implements QueryExecutor<PsiElement, P
         }
 
 
-        final TypeEvalContext context = queryParameters.getContext();
         if (superMethod == null && context != null) {
           // If super method still not found and we have context, we may use it to find method
           final PyClassLikeType classLikeType = PyUtil.as(context.getType(superClass), PyClassLikeType.class);
           if (classLikeType != null) {
-            for (final PyFunction function : PyClassLikeTypeUtil.getMembersOfType(classLikeType, PyFunction.class, context)) {
+            for (PyFunction function : PyClassLikeTypeUtil.getMembersOfType(classLikeType, PyFunction.class, context)) {
               final String elemName = function.getName();
-              if (elemName != null && elemName.equals(queryParameters.getDerivedMethod().getName())) {
+              if (elemName != null && elemName.equals(func.getName())) {
                 consumer.process(function);
               }
             }
diff --git a/python/src/com/jetbrains/python/refactoring/PyBaseRefactoringAction.java b/python/src/com/jetbrains/python/refactoring/PyBaseRefactoringAction.java
new file mode 100644 (file)
index 0000000..60d076d
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.actions.BaseRefactoringAction;
+import com.jetbrains.python.PythonLanguage;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Mikhail Golubev
+ */
+public abstract class PyBaseRefactoringAction extends BaseRefactoringAction {
+
+  protected abstract boolean isEnabledOnElementInsideEditor(@NotNull PsiElement element,
+                                                            @NotNull Editor editor,
+                                                            @NotNull PsiFile file,
+                                                            @NotNull DataContext context);
+
+  @Override
+  protected final boolean isAvailableOnElementInEditorAndFile(@NotNull PsiElement element,
+                                                              @NotNull Editor editor,
+                                                              @NotNull PsiFile file,
+                                                              @NotNull DataContext context) {
+    return isEnabledOnElementInsideEditor(element, editor, file, context);
+  }
+
+  protected abstract boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements);
+
+  @Override
+  protected final boolean isEnabledOnElements(@NotNull PsiElement[] elements) {
+    return isEnabledOnElementsOutsideEditor(elements);
+  }
+
+  @Override
+  protected final boolean isAvailableForLanguage(Language language) {
+    return language.isKindOf(PythonLanguage.getInstance());
+  }
+
+  @Override
+  protected boolean isAvailableForFile(PsiFile file) {
+    return isAvailableForLanguage(file.getLanguage()) && !isLibraryFile(file);
+  }
+
+  private static boolean isLibraryFile(@NotNull PsiFile file) {
+    final VirtualFile virtualFile = file.getVirtualFile();
+    if (virtualFile != null && ProjectRootManager.getInstance(file.getProject()).getFileIndex().isInLibraryClasses(virtualFile)) {
+      return true;
+    }
+    return false;
+  }
+}
index 03711d3d22ec8f878f7fd3c2d65c11d93a1743c1..db4b8d217a558c86bc05db72a012a88dbc6275fa 100644 (file)
@@ -41,6 +41,9 @@ import java.util.*;
  * Time: 7:07:02 PM
  */
 public class PyRefactoringUtil {
+  private PyRefactoringUtil() {
+  }
+
   @NotNull
   public static List<PsiElement> getOccurrences(@NotNull final PsiElement pattern, @Nullable final PsiElement context) {
     if (context == null) {
@@ -279,9 +282,6 @@ public class PyRefactoringUtil {
     return PsiUtilCore.toPsiElementArray(array);
   }
 
-  private PyRefactoringUtil() {
-  }
-
   public static boolean areConflictingMethods(PyFunction pyFunction, PyFunction pyFunction1) {
     final PyParameter[] firstParams = pyFunction.getParameterList().getParameters();
     final PyParameter[] secondParams = pyFunction1.getParameterList().getParameters();
similarity index 61%
rename from python/src/com/jetbrains/python/refactoring/convert/PyBaseConvertRefactoringAction.java
rename to python/src/com/jetbrains/python/refactoring/convertModulePackage/PyBaseConvertModulePackageAction.java
index 9b1866d474d9d4a2e3ae41c78be30701ad2fdc2c..91d166405f473a95c3fbf907e2f0b674aaa268ef 100644 (file)
@@ -1,6 +1,5 @@
-package com.jetbrains.python.refactoring.convert;
+package com.jetbrains.python.refactoring.convertModulePackage;
 
-import com.intellij.lang.Language;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.project.Project;
@@ -8,39 +7,20 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.refactoring.RefactoringBundle;
-import com.intellij.refactoring.actions.BaseRefactoringAction;
 import com.intellij.refactoring.util.CommonRefactoringUtil;
 import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.PythonLanguage;
+import com.jetbrains.python.refactoring.PyBaseRefactoringAction;
 import org.jetbrains.annotations.NotNull;
 
 /**
  * @author Mikhail Golubev
  */
-public abstract class PyBaseConvertRefactoringAction extends BaseRefactoringAction {
+public abstract class PyBaseConvertModulePackageAction extends PyBaseRefactoringAction {
   @Override
   protected final boolean isAvailableInEditorOnly() {
     return false;
   }
 
-  @Override
-  protected boolean isAvailableOnElementInEditorAndFile(@NotNull PsiElement element,
-                                                        @NotNull Editor editor,
-                                                        @NotNull PsiFile file,
-                                                        @NotNull DataContext context) {
-    return false;
-  }
-
-  @Override
-  protected final boolean isAvailableForLanguage(Language language) {
-    return language.isKindOf(PythonLanguage.getInstance());
-  }
-
-  @Override
-  protected boolean isAvailableForFile(PsiFile file) {
-    return isAvailableForLanguage(file.getLanguage());
-  }
-
   /**
    * Show standard error dialog containing message about unexpected presense of given file or directory.
    *
@@ -58,4 +38,12 @@ public abstract class PyBaseConvertRefactoringAction extends BaseRefactoringActi
     }
     CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, id, project);
   }
+
+  @Override
+  protected boolean isEnabledOnElementInsideEditor(@NotNull PsiElement element,
+                                                   @NotNull Editor editor,
+                                                   @NotNull PsiFile file,
+                                                   @NotNull DataContext context) {
+    return false;
+  }
 }
similarity index 93%
rename from python/src/com/jetbrains/python/refactoring/convert/PyConvertModuleToPackageAction.java
rename to python/src/com/jetbrains/python/refactoring/convertModulePackage/PyConvertModuleToPackageAction.java
index 6716d82a7d078431e3477c746359df6f046388e4..2808e806af71bde5706228bb9c12af6f32b47539 100644 (file)
@@ -1,4 +1,4 @@
-package com.jetbrains.python.refactoring.convert;
+package com.jetbrains.python.refactoring.convertModulePackage;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.intellij.openapi.actionSystem.DataContext;
@@ -21,12 +21,12 @@ import java.io.IOException;
 /**
  * @author Mikhail Golubev
  */
-public class PyConvertModuleToPackageAction extends PyBaseConvertRefactoringAction {
+public class PyConvertModuleToPackageAction extends PyBaseConvertModulePackageAction {
   public static final String ID = "py.refactoring.convert.module.to.package";
   private static final Logger LOG = Logger.getInstance(PyConvertModuleToPackageAction.class);
 
   @Override
-  protected boolean isEnabledOnElements(@NotNull PsiElement[] elements) {
+  protected boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements) {
     if (elements.length == 1) {
       return elements[0] instanceof PyFile && !PyUtil.isPackage((PyFile)elements[0]);
     }
similarity index 96%
rename from python/src/com/jetbrains/python/refactoring/convert/PyConvertPackageToModuleAction.java
rename to python/src/com/jetbrains/python/refactoring/convertModulePackage/PyConvertPackageToModuleAction.java
index 6a0b8f421112e10dc7f5f6b52f7ed3d2b33deade..70496fed58fe8a6d8d75a1bff354891cc2734502 100644 (file)
@@ -1,4 +1,4 @@
-package com.jetbrains.python.refactoring.convert;
+package com.jetbrains.python.refactoring.convertModulePackage;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.intellij.openapi.actionSystem.DataContext;
@@ -29,12 +29,12 @@ import static com.jetbrains.python.psi.PyUtil.as;
 /**
  * @author Mikhail Golubev
  */
-public class PyConvertPackageToModuleAction extends PyBaseConvertRefactoringAction {
+public class PyConvertPackageToModuleAction extends PyBaseConvertModulePackageAction {
   private static final Logger LOG = Logger.getInstance(PyConvertPackageToModuleAction.class);
   private static final String ID = "py.refactoring.convert.package.to.module";
 
   @Override
-  protected boolean isEnabledOnElements(@NotNull PsiElement[] elements) {
+  protected boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements) {
     if (elements.length == 1) {
       final PsiDirectory pyPackage = getPackageDir(elements[0]);
       return pyPackage != null && !isSpecialDirectory(pyPackage);
index b0ffe97eec0bedffad0b568885b4c5a19d8352f4..9a7161c8d057bf3dcc8c6737e0ed03a1961dc1d1 100644 (file)
@@ -489,7 +489,7 @@ public class PyExtractMethodUtil {
     final PsiNamedElement parent = PsiTreeUtil.getParentOfType(anchor, PyFile.class, PyClass.class, PyFunction.class);
 
     final PsiElement result;
-    // The only safe case to insert extracted function *after* original scope owner is function.
+    // The only safe case to insert extracted function *after* the original scope owner is when it's function.
     if (parent instanceof PyFunction) {
       result = parent.getParent().addAfter(generatedMethod, parent);
     }
diff --git a/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyBaseMakeFunctionTopLevelProcessor.java b/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyBaseMakeFunctionTopLevelProcessor.java
new file mode 100644 (file)
index 0000000..14db3d5
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * 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.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;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.refactoring.BaseRefactoringProcessor;
+import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.usageView.UsageViewDescriptor;
+import com.intellij.util.ArrayUtil;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
+import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyPsiUtils;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import com.jetbrains.python.refactoring.PyRefactoringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.jetbrains.python.psi.PyUtil.as;
+
+/**
+ * @author Mikhail Golubev
+ */
+public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
+  protected final PyFunction myFunction;
+  protected final PyResolveContext myResolveContext;
+  protected final Editor myEditor;
+  protected final PyElementGenerator myGenerator;
+
+  public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
+    super(targetFunction.getProject());
+    myFunction = targetFunction;
+    myEditor = editor;
+    final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
+    myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
+    myGenerator = PyElementGenerator.getInstance(myProject);
+  }
+
+  @NotNull
+  @Override
+  protected final UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
+    return new UsageViewDescriptorAdapter() {
+      @NotNull
+      @Override
+      public PsiElement[] getElements() {
+        return new PsiElement[] {myFunction};
+      }
+
+      @Override
+      public String getProcessedElementsHeader() {
+        return getRefactoringName();
+      }
+    };
+  }
+
+  @NotNull
+  @Override
+  protected final UsageInfo[] findUsages() {
+    return ArrayUtil.toObjectArray(PyRefactoringUtil.findUsages(myFunction, false), UsageInfo.class);
+  }
+
+  @Override
+  protected final String getCommandName() {
+    return getRefactoringName();
+  }
+
+  @Override
+  protected final void performRefactoring(@NotNull UsageInfo[] usages) {
+    final List<String> newParameters = collectNewParameterNames();
+
+    assert ApplicationManager.getApplication().isWriteAccessAllowed();
+
+    updateUsages(newParameters, usages);
+    final PyFunction newFunction = replaceFunction(createNewFunction(newParameters));
+
+    myEditor.getSelectionModel().removeSelection();
+    myEditor.getCaretModel().moveToOffset(newFunction.getTextOffset());
+  }
+
+  @NotNull
+  protected abstract String getRefactoringName();
+
+  @NotNull
+  protected abstract List<String> collectNewParameterNames();
+
+  protected abstract void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages);
+  
+  @NotNull
+  protected abstract PyFunction createNewFunction(@NotNull Collection<String> newParamNames);
+
+  @NotNull
+  protected final PyParameterList addParameters(@NotNull PyParameterList paramList, @NotNull Collection<String> newParameters) {
+    if (!newParameters.isEmpty()) {
+      final String commaSeparatedNames = StringUtil.join(newParameters, ", ");
+      final StringBuilder paramListText = new StringBuilder(paramList.getText());
+      paramListText.insert(1, commaSeparatedNames + (paramList.getParameters().length > 0 ? ", " : ""));
+      final PyParameterList newElement = myGenerator.createParameterList(LanguageLevel.forElement(myFunction), paramListText.toString());
+      return (PyParameterList)paramList.replace(newElement);
+    }
+    return paramList;
+  }
+
+  @NotNull
+  protected PyArgumentList addArguments(@NotNull PyArgumentList argList, @NotNull Collection<String> newArguments) {
+    if (!newArguments.isEmpty()) {
+      final String commaSeparatedNames = StringUtil.join(newArguments, ", ");
+      final StringBuilder argListText = new StringBuilder(argList.getText());
+      argListText.insert(1, commaSeparatedNames + (argList.getArguments().length > 0 ? ", " : ""));
+      final PyArgumentList newElement = myGenerator.createArgumentList(LanguageLevel.forElement(argList), argListText.toString());
+      return (PyArgumentList)argList.replace(newElement);
+    }
+    return argList;
+  }
+
+  @NotNull
+  protected PyFunction replaceFunction(@NotNull PyFunction newFunction) {
+    final PsiFile file = myFunction.getContainingFile();
+    final PsiElement anchor = PyPsiUtils.getParentRightBefore(myFunction, file);
+
+    myFunction.delete();
+    return (PyFunction)file.addAfter(newFunction, anchor);
+  }
+
+  @NotNull
+  protected AnalysisResult analyseScope(@NotNull ScopeOwner owner) {
+    final ControlFlow controlFlow = ControlFlowCache.getControlFlow(owner);
+    final AnalysisResult result = new AnalysisResult();
+    for (Instruction instruction : controlFlow.getInstructions()) {
+      if (instruction instanceof ReadWriteInstruction) {
+        final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction;
+        final PsiElement element = readWriteInstruction.getElement();
+        if (element == null) {
+          continue;
+        }
+        if (readWriteInstruction.getAccess().isReadAccess()) {
+          for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
+            if (resolved != null) {
+              if (isInitOrNewMethod(resolved)) {
+                resolved = ((PyFunction)resolved).getContainingClass();
+              }
+              if (isFromEnclosingScope(resolved)) {
+                result.readsFromEnclosingScope.add(element);
+              }
+              if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf()) {
+                if (PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
+                  result.readsOfSelfParameter.add(element);
+                }
+                else if (!PsiTreeUtil.isAncestor(myFunction, resolved, true)) {
+                  result.readsOfSelfParametersFromEnclosingScope.add(element);
+                }
+              }
+            }
+          }
+        }
+        if (readWriteInstruction.getAccess().isWriteAccess() && element instanceof PyTargetExpression) {
+          for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
+            if (resolved != null) {
+              if (element.getParent() instanceof PyNonlocalStatement && isFromEnclosingScope(resolved)) {
+                result.nonlocalWritesToEnclosingScope.add((PyTargetExpression)element);
+              }
+              if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf() && 
+                  PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
+                result.writesToSelfParameter.add((PyTargetExpression)element);
+              }
+            }
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  private static boolean isInitOrNewMethod(@NotNull PsiElement elem) {
+    final PyFunction func = as(elem, PyFunction.class);
+    return func != null && (PyNames.INIT.equals(func.getName()) || PyNames.NEW.equals(func.getName()));
+  }
+
+  private boolean isFromEnclosingScope(@NotNull PsiElement element) {
+    return element.getContainingFile() == myFunction.getContainingFile() &&
+           !PsiTreeUtil.isAncestor(myFunction, element, false) &&
+           !(ScopeUtil.getScopeOwner(element) instanceof PsiFile); 
+  }
+
+  protected static class AnalysisResult {
+    final List<PsiElement> readsFromEnclosingScope = new ArrayList<PsiElement>();
+    final List<PyTargetExpression> nonlocalWritesToEnclosingScope = new ArrayList<PyTargetExpression>();
+    final List<PsiElement> readsOfSelfParametersFromEnclosingScope = new ArrayList<PsiElement>();
+    final List<PsiElement> readsOfSelfParameter = new ArrayList<PsiElement>();
+    // No one writes to "self", but handle this case too just to be sure
+    final List<PyTargetExpression> writesToSelfParameter = new ArrayList<PyTargetExpression>();
+  }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeFunctionTopLevelRefactoring.java b/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeFunctionTopLevelRefactoring.java
new file mode 100644 (file)
index 0000000..f639fe8
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * 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
+      }
+    };
+  }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeLocalFunctionTopLevelProcessor.java b/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeLocalFunctionTopLevelProcessor.java
new file mode 100644 (file)
index 0000000..757c2c3
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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.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;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.containers.ContainerUtil;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.psi.PyCallExpression;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.jetbrains.python.psi.PyUtil.as;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyMakeLocalFunctionTopLevelProcessor extends PyBaseMakeFunctionTopLevelProcessor {
+
+  protected PyMakeLocalFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
+    super(targetFunction, editor);
+    setPreviewUsages(false);
+  }
+
+  @Override
+  @NotNull
+  protected String getRefactoringName() {
+    return PyBundle.message("refactoring.make.local.function.top.level");
+  }
+
+  @Override
+  protected void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages) {
+    for (UsageInfo usage : usages) {
+      final PsiElement element = usage.getElement();
+      if (element != null) {
+        final PyCallExpression parentCall = as(element.getParent(), PyCallExpression.class);
+        if (parentCall != null && parentCall.getArgumentList() != null) {
+          addArguments(parentCall.getArgumentList(), newParamNames);
+        }
+      }
+    }
+  }
+
+  @Override
+  @NotNull
+  protected PyFunction createNewFunction(@NotNull Collection<String> newParamNames) {
+    final PyFunction copied = (PyFunction)myFunction.copy();
+    addParameters(copied.getParameterList(), newParamNames);
+    return copied;
+  }
+
+  @Override
+  @NotNull
+  protected List<String> collectNewParameterNames() {
+    final Set<String> enclosingScopeReads = new LinkedHashSet<String>();
+    for (ScopeOwner owner : PsiTreeUtil.collectElementsOfType(myFunction, ScopeOwner.class)) {
+      final AnalysisResult result = analyseScope(owner);
+      if (!result.nonlocalWritesToEnclosingScope.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.nonlocal.writes"));
+      }
+      if (!result.readsOfSelfParametersFromEnclosingScope.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.self.reads"));
+      }
+      for (PsiElement element : result.readsFromEnclosingScope) {
+        if (element instanceof PyElement) {
+          ContainerUtil.addIfNotNull(enclosingScopeReads, ((PyElement)element).getName());
+        }
+      }
+    }
+    return Lists.newArrayList(enclosingScopeReads);
+  }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeMethodTopLevelProcessor.java b/python/src/com/jetbrains/python/refactoring/makeFunctionTopLevel/PyMakeMethodTopLevelProcessor.java
new file mode 100644 (file)
index 0000000..ae516b0
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * 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.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;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.Function;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.containers.MultiMap;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.codeInsight.imports.AddImportHelper;
+import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
+import com.jetbrains.python.refactoring.NameSuggesterUtil;
+import com.jetbrains.python.refactoring.introduce.IntroduceValidator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+import static com.jetbrains.python.psi.PyUtil.as;
+
+/**
+ * @author Mikhail Golubev
+ */
+public class PyMakeMethodTopLevelProcessor extends PyBaseMakeFunctionTopLevelProcessor {
+
+  private final LinkedHashMap<String, String> myAttributeToParameterName = new LinkedHashMap<String, String>();
+  private final MultiMap<String, PyReferenceExpression> myAttributeReferences = MultiMap.create();
+  private final Set<PsiElement> myReadsOfSelfParam = new HashSet<PsiElement>();
+
+  public PyMakeMethodTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
+    super(targetFunction, editor);
+    // It's easier to debug without preview
+    setPreviewUsages(!ApplicationManager.getApplication().isInternal());
+  }
+
+  @NotNull
+  @Override
+  protected String getRefactoringName() {
+    return PyBundle.message("refactoring.make.method.top.level");
+  }
+
+  @Override
+  protected void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages) {
+    // Field usages
+    for (String attrName : myAttributeReferences.keySet()) {
+      final Collection<PyReferenceExpression> reads = myAttributeReferences.get(attrName);
+      final String paramName = myAttributeToParameterName.get(attrName);
+      if (!attrName.equals(paramName)) {
+        for (PyReferenceExpression read : reads) {
+          read.replace(myGenerator.createExpressionFromText(LanguageLevel.forElement(read), paramName));
+        }
+      }
+      else {
+        for (PyReferenceExpression read : reads) {
+          removeQualifier(read);
+        }
+      }
+    }
+
+    // Function usages
+    final Collection<String> attrNames = myAttributeToParameterName.keySet();
+    for (UsageInfo usage : usages) {
+      final PsiElement usageElem = usage.getElement();
+      if (usageElem == null) {
+        continue;
+      }
+
+      if (usageElem instanceof PyReferenceExpression) {
+        final PyExpression qualifier = ((PyReferenceExpression)usageElem).getQualifier();
+        final PyCallExpression callExpr = as(usageElem.getParent(), PyCallExpression.class);
+        if (qualifier != null && callExpr != null && callExpr.getArgumentList() != null) {
+          PyExpression instanceExpr = qualifier;
+          final PyArgumentList argumentList = callExpr.getArgumentList();
+          
+          // Class.method(instance) -> method(instance)
+          if (resolvesToClass(qualifier)) {
+            final PyExpression[] arguments = argumentList.getArguments();
+            if (arguments.length > 0) {
+              instanceExpr = arguments[0];
+              instanceExpr.delete();
+            }
+            else {
+              // It's not clear how to handle usages like Class.method(), since there is no suitable instance
+              instanceExpr = null;
+            }
+          }
+
+          if (instanceExpr != null) {
+            // module.inst.method() -> method(module.inst.foo, module.inst.bar)
+            if (isPureReferenceExpression(instanceExpr)) {
+              // recursive call inside the method
+              if (myReadsOfSelfParam.contains(instanceExpr)) {
+                addArguments(argumentList, newParamNames);
+              }
+              else {
+                final String instanceExprText = instanceExpr.getText();
+                addArguments(argumentList, ContainerUtil.map(attrNames, new Function<String, String>() {
+                  @Override
+                  public String fun(String attribute) {
+                    return instanceExprText + "." + attribute;
+                  }
+                }));
+              }
+            }
+            // Class().method() -> method(Class().foo)
+            else if (newParamNames.size() == 1) {
+              addArguments(argumentList, Collections.singleton(instanceExpr.getText() + "." + ContainerUtil.getFirstItem(attrNames)));
+            }
+            // Class().method() -> inst = Class(); method(inst.foo, inst.bar)
+            else if (!newParamNames.isEmpty()) {
+              final PyStatement anchor = PsiTreeUtil.getParentOfType(callExpr, PyStatement.class);
+              final String targetName = selectUniqueName(usageElem);
+              final String assignmentText = targetName + " = " + instanceExpr.getText();
+              final PyAssignmentStatement assignment = myGenerator.createFromText(LanguageLevel.forElement(callExpr),
+                                                                                  PyAssignmentStatement.class,
+                                                                                  assignmentText);
+              //noinspection ConstantConditions
+              anchor.getParent().addBefore(assignment, anchor);
+              addArguments(argumentList, ContainerUtil.map(attrNames, new Function<String, String>() {
+                @Override
+                public String fun(String attribute) {
+                  return targetName + "." + attribute;
+                }
+              }));
+            }
+          }
+        }
+        
+        final PsiFile usageFile = usage.getFile();
+        final PsiFile origFile = myFunction.getContainingFile();
+        if (usageFile != origFile) {
+          final String funcName = myFunction.getName();
+          final String origModuleName = QualifiedNameFinder.findShortestImportableName(origFile, origFile.getVirtualFile());
+          if (usageFile != null && origModuleName != null && funcName != null) {
+            AddImportHelper.addOrUpdateFromImportStatement(usageFile, origModuleName, funcName, null, ImportPriority.PROJECT, null);
+          }
+        }
+
+        // Will replace/invalidate entire expression
+        removeQualifier((PyReferenceExpression)usageElem);
+      }
+    }
+  }
+
+  @NotNull
+  private String selectUniqueName(@NotNull PsiElement scopeAnchor) {
+    final PyClass pyClass = myFunction.getContainingClass();
+    assert pyClass != null;
+    final Collection<String> suggestions;
+    if (pyClass.getName() != null) {
+      suggestions = NameSuggesterUtil.generateNamesByType(pyClass.getName());
+    }
+    else {
+      suggestions = Collections.singleton("inst");
+    }
+    for (String name : suggestions) {
+      if (isValidName(name, scopeAnchor)) {
+        return name;
+      }
+    }
+
+    //noinspection ConstantConditions
+    return appendNumberUntilValid(Iterables.getLast(suggestions), scopeAnchor);
+  }
+
+  private static boolean isValidName(@NotNull String name, @NotNull PsiElement scopeAnchor) {
+    return !(IntroduceValidator.isDefinedInScope(name, scopeAnchor) || PyNames.isReserved(name));
+  }
+
+  @NotNull
+  private static String appendNumberUntilValid(@NotNull String name, @NotNull PsiElement scopeAnchor) {
+    int counter = 1;
+    String candidate = name;
+    while (!isValidName(candidate, scopeAnchor)) {
+      candidate = name + counter;
+      counter++;
+    }
+    return candidate;
+  }
+
+  private boolean resolvesToClass(@NotNull PyExpression qualifier) {
+    for (PsiElement element : PyUtil.multiResolveTopPriority(qualifier, myResolveContext)) {
+      if (element == myFunction.getContainingClass()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean isPureReferenceExpression(@NotNull PyExpression expr) {
+    if (!(expr instanceof PyReferenceExpression)) {
+      return false;
+    }
+    final PyExpression qualifier = ((PyReferenceExpression)expr).getQualifier();
+    return qualifier == null || isPureReferenceExpression(qualifier);
+  }
+
+  @NotNull
+  private PyReferenceExpression removeQualifier(@NotNull PyReferenceExpression expr) {
+    if (!expr.isQualified()) {
+      return expr;
+    }
+    final PyExpression newExpression = myGenerator.createExpressionFromText(LanguageLevel.forElement(expr), expr.getLastChild().getText());
+    return (PyReferenceExpression)expr.replace(newExpression);
+  }
+
+  @NotNull
+  @Override
+  protected PyFunction createNewFunction(@NotNull Collection<String> newParams) {
+    final PyFunction copied = (PyFunction)myFunction.copy();
+    final PyParameter[] params = copied.getParameterList().getParameters();
+    if (params.length > 0) {
+      params[0].delete();
+    }
+    addParameters(copied.getParameterList(), newParams);
+    return copied;
+  }
+
+  @NotNull
+  @Override
+  protected List<String> collectNewParameterNames() {
+    final Set<String> attributeNames = new LinkedHashSet<String>();
+    for (ScopeOwner owner : PsiTreeUtil.collectElementsOfType(myFunction, ScopeOwner.class)) {
+      final AnalysisResult result =  analyseScope(owner);
+      if (!result.nonlocalWritesToEnclosingScope.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.nonlocal.writes"));
+      }
+      if (!result.readsOfSelfParametersFromEnclosingScope.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.self.reads"));
+      }
+      if (!result.readsFromEnclosingScope.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.outer.scope.reads"));
+      }
+      if (!result.writesToSelfParameter.isEmpty()) {
+        throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.special.usage.of.self"));
+      }
+      myReadsOfSelfParam.addAll(result.readsOfSelfParameter);
+      for (PsiElement usage : result.readsOfSelfParameter) {
+        if (usage.getParent() instanceof PyTargetExpression) {
+          throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.attribute.writes"));
+        }
+        final PyReferenceExpression parentReference = as(usage.getParent(), PyReferenceExpression.class);
+        if (parentReference != null) {
+          final String attrName = parentReference.getName();
+          if (attrName != null && PyUtil.isClassPrivateName(attrName)) {
+            throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.private.attributes"));
+          }
+          if (parentReference.getParent() instanceof PyCallExpression) {
+            if (!(Comparing.equal(myFunction.getName(), parentReference.getName()))) {
+              throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.method.calls"));
+            }
+            else {
+              // do not add method itself to its parameters
+              continue;
+            }
+          }
+          attributeNames.add(attrName);
+          myAttributeReferences.putValue(attrName, parentReference);
+        }
+        else {
+          throw new IncorrectOperationException(PyBundle.message("refactoring.make.function.top.level.error.special.usage.of.self"));
+        }
+      }
+    }
+    for (String name : attributeNames) {
+      final Collection<PyReferenceExpression> reads = myAttributeReferences.get(name);
+      final PsiElement anchor = ContainerUtil.getFirstItem(reads);
+      //noinspection ConstantConditions
+      if (!isValidName(name, anchor)) {
+        final String indexedName = appendNumberUntilValid(name, anchor);
+        myAttributeToParameterName.put(name, indexedName);
+      }
+      else {
+        myAttributeToParameterName.put(name, name);
+      }
+    }
+    return Lists.newArrayList(myAttributeToParameterName.values());
+  }
+}
diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers.py
new file mode 100644 (file)
index 0000000..317c0b1
--- /dev/null
@@ -0,0 +1,8 @@
+class C:
+    def me<caret>thod(self, x):
+        test = 1
+        
+
+inst = C()      
+inst . method(1)
+C . method(inst, 42)
diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/removeQualifiers_after.py
new file mode 100644 (file)
index 0000000..53e025d
--- /dev/null
@@ -0,0 +1,11 @@
+def method(x):
+    test = 1
+
+
+class C:
+    pass
+
+
+inst = C()
+method(1)
+method(42)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferenceToOuterScope.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferenceToOuterScope.py
new file mode 100644 (file)
index 0000000..54b6286
--- /dev/null
@@ -0,0 +1,7 @@
+def func():
+    x = True
+    def loc<caret>al():
+        def nested():
+            nonlocal x
+            x = False
+            
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.after.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.after.py
new file mode 100644 (file)
index 0000000..d19afe6
--- /dev/null
@@ -0,0 +1,9 @@
+def func():
+    pass
+
+
+def local():
+    x = True
+    def nested():
+        nonlocal x
+        x = False
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionNonlocalReferencesInInnerFunction.py
new file mode 100644 (file)
index 0000000..ca46376
--- /dev/null
@@ -0,0 +1,6 @@
+def func():
+    def lo<caret>cal():
+        x = True
+        def nested():
+            nonlocal x
+            x = False
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionReferenceToSelf.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionReferenceToSelf.py
new file mode 100644 (file)
index 0000000..437494d
--- /dev/null
@@ -0,0 +1,4 @@
+class C:
+    def m(self):
+        def lo<caret>cal():
+            print(self)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.after.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.after.py
new file mode 100644 (file)
index 0000000..790bd89
--- /dev/null
@@ -0,0 +1,14 @@
+global_var = 'spam'
+
+
+def enclosing(p1, p2):
+    x = 42
+
+    local(p1, x, 'foo')
+
+
+def local(p1, x, p):
+    def nested():
+        print(p, x)
+
+    print(p1, p)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.py b/python/testData/refactoring/makeFunctionTopLevel/localFunctionSimple.py
new file mode 100644 (file)
index 0000000..262beac
--- /dev/null
@@ -0,0 +1,13 @@
+global_var = 'spam'
+
+
+def enclosing(p1, p2):
+    x = 42
+
+    def lo<caret>cal(p):
+        def nested():
+            print(p, x)
+
+        print(p1, p)
+
+    local('foo')
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodAttributeWrites.py b/python/testData/refactoring/makeFunctionTopLevel/methodAttributeWrites.py
new file mode 100644 (file)
index 0000000..a78728a
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def __init__(self):
+        self.attr = True
+
+    def me<caret>thod(self):
+        self.attr = False
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.after.py
new file mode 100644 (file)
index 0000000..d48abb5
--- /dev/null
@@ -0,0 +1,11 @@
+class C():
+    pass
+
+
+def method(foo, bar, x):
+    print(foo, bar, x)
+
+
+c = C()
+method(c.foo, c.bar, 42)
+method()
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.py b/python/testData/refactoring/makeFunctionTopLevel/methodCalledViaClass.py
new file mode 100644 (file)
index 0000000..1245f69
--- /dev/null
@@ -0,0 +1,7 @@
+class C():
+    def me<caret>thod(self, x):
+        print(self.foo, self.bar, x)
+        
+        
+C.method(C(), 42)
+C.method()
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/main.py b/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/main.py
new file mode 100644 (file)
index 0000000..203ab74
--- /dev/null
@@ -0,0 +1,7 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+
+
+def method(foo, x):
+    print(foo)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/other.py b/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/after/other.py
new file mode 100644 (file)
index 0000000..3fd9780
--- /dev/null
@@ -0,0 +1,4 @@
+from main import C, method
+
+inst = C()
+method(inst.foo, 42)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/main.py b/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/main.py
new file mode 100644 (file)
index 0000000..5335d08
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+
+    def me<caret>thod(self, x):
+        print(self.foo)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/other.py b/python/testData/refactoring/makeFunctionTopLevel/methodImportUpdates/before/other.py
new file mode 100644 (file)
index 0000000..c97b9d9
--- /dev/null
@@ -0,0 +1,4 @@
+from main import C
+
+inst = C()
+inst.method(42)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.after.py
new file mode 100644 (file)
index 0000000..5cfeff1
--- /dev/null
@@ -0,0 +1,12 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+        self.bar = 'spam'
+
+
+def method(foo, bar, x):
+    print(foo, bar)
+
+
+c = C()
+method(c.foo, c.bar, 1)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.py b/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesConstructorQualifier.py
new file mode 100644 (file)
index 0000000..f5b267b
--- /dev/null
@@ -0,0 +1,10 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+        self.bar = 'spam'
+
+    def me<caret>thod(self, x):
+        print(self.foo, self.bar)
+
+
+C().method(1)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.after.py
new file mode 100644 (file)
index 0000000..d1bf980
--- /dev/null
@@ -0,0 +1,12 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+        self.bar = 'spam'
+
+
+def method(foo, bar, x):
+    print(foo, bar)
+
+
+inst = C()
+method(inst.foo, inst.bar, 1)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.py b/python/testData/refactoring/makeFunctionTopLevel/methodMultipleAttributesReadReferenceQualifier.py
new file mode 100644 (file)
index 0000000..bd66ea0
--- /dev/null
@@ -0,0 +1,11 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+        self.bar = 'spam'
+
+    def me<caret>thod(self, x):
+        print(self.foo, self.bar)
+
+
+inst = C()
+inst.method(1)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.after.py
new file mode 100644 (file)
index 0000000..fc372af
--- /dev/null
@@ -0,0 +1,10 @@
+class C:
+    pass
+
+
+def method(x):
+    pass
+
+
+c = C()        
+method(1)        
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.py b/python/testData/refactoring/makeFunctionTopLevel/methodNoNewParams.py
new file mode 100644 (file)
index 0000000..ee6f712
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def me<caret>thod(self, x):
+        pass
+        
+c = C()        
+c.method(1)        
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodNonlocalReferenceToOuterScope.py b/python/testData/refactoring/makeFunctionTopLevel/methodNonlocalReferenceToOuterScope.py
new file mode 100644 (file)
index 0000000..6f912dd
--- /dev/null
@@ -0,0 +1,6 @@
+def func():
+    x = True
+    class C:
+        def me<caret>thod(self):
+            nonlocal x
+            x = False
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodOtherMethodCalls.py b/python/testData/refactoring/makeFunctionTopLevel/methodOtherMethodCalls.py
new file mode 100644 (file)
index 0000000..64ba209
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def me<caret>thod(self):
+        self.another()
+    
+    def another(self):
+        pass
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodOuterScopeReads.py b/python/testData/refactoring/makeFunctionTopLevel/methodOuterScopeReads.py
new file mode 100644 (file)
index 0000000..840257a
--- /dev/null
@@ -0,0 +1,5 @@
+def func():
+    x = True
+    class C:
+        def me<caret>thod(self):
+            print(x)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodOverriddenSelf.py b/python/testData/refactoring/makeFunctionTopLevel/methodOverriddenSelf.py
new file mode 100644 (file)
index 0000000..1c3b86c
--- /dev/null
@@ -0,0 +1,3 @@
+class C:
+    def me<caret>thod(self):
+        self = object()
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodReadPrivateAttributes.py b/python/testData/refactoring/makeFunctionTopLevel/methodReadPrivateAttributes.py
new file mode 100644 (file)
index 0000000..71b58b0
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def __init__(self):
+        self.__attr = True
+
+    def me<caret>thod(self):
+        print(self.__attr)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodSelfUsedAsOperand.py b/python/testData/refactoring/makeFunctionTopLevel/methodSelfUsedAsOperand.py
new file mode 100644 (file)
index 0000000..72b1108
--- /dev/null
@@ -0,0 +1,3 @@
+class C(int):
+    def me<caret>thod(self):
+        print(self + 42)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.after.py
new file mode 100644 (file)
index 0000000..e8e0fba
--- /dev/null
@@ -0,0 +1,10 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+
+
+def method(foo, x):
+    print(foo)
+
+
+method(C().foo, 1)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.py b/python/testData/refactoring/makeFunctionTopLevel/methodSingleAttributeRead.py
new file mode 100644 (file)
index 0000000..82c8886
--- /dev/null
@@ -0,0 +1,9 @@
+class C:
+    def __init__(self):
+        self.foo = 42
+
+    def me<caret>thod(self, x):
+        print(self.foo)
+
+
+C().method(1)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.after.py
new file mode 100644 (file)
index 0000000..9dbb95a
--- /dev/null
@@ -0,0 +1,12 @@
+class AbstractBaseResponseHandler:
+    pass
+
+
+def method(response, code):
+    if response:
+       return code
+
+
+def func(abstract_base_response_handler, a):
+    a1 = AbstractBaseResponseHandler()
+    method(a1.response, a1.code)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.py b/python/testData/refactoring/makeFunctionTopLevel/methodUniqueNameOfExtractedQualifier.py
new file mode 100644 (file)
index 0000000..b85b016
--- /dev/null
@@ -0,0 +1,8 @@
+class AbstractBaseResponseHandler:
+    def me<caret>thod(self):
+        if self.response:
+           return self.code
+        
+
+def func(abstract_base_response_handler, a):
+    AbstractBaseResponseHandler().method()
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.after.py b/python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.after.py
new file mode 100644 (file)
index 0000000..6565bb1
--- /dev/null
@@ -0,0 +1,10 @@
+class C:
+    pass
+
+
+def method(foo2, bar1, foo, foo1, bar):
+    print(foo2, bar1)
+
+
+c = C()
+method(c.foo, c.bar, 1, 2, bar=3)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.py b/python/testData/refactoring/makeFunctionTopLevel/methodUniqueParamNames.py
new file mode 100644 (file)
index 0000000..7e1165a
--- /dev/null
@@ -0,0 +1,6 @@
+class C:
+    def me<caret>thod(self, foo, foo1, bar):
+        print(self.foo, self.bar)
+        
+
+C().method(1, 2, bar=3)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.after.py b/python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.after.py
new file mode 100644 (file)
index 0000000..19ba9c4
--- /dev/null
@@ -0,0 +1,8 @@
+def f(x):
+    z = 42
+    g(x, z, 42)
+
+
+def g(x, z, y):
+    print(x, y, z)
+    g(x, z, x)
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.py b/python/testData/refactoring/makeFunctionTopLevel/recursiveLocalFunction.py
new file mode 100644 (file)
index 0000000..74df2e9
--- /dev/null
@@ -0,0 +1,6 @@
+def f(x):
+    def <caret>g(y):
+        print(x, y, z)
+        g(x)
+    z = 42
+    g(42)   
\ No newline at end of file
diff --git a/python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.after.py b/python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.after.py
new file mode 100644 (file)
index 0000000..f8b6594
--- /dev/null
@@ -0,0 +1,8 @@
+class C:
+    def __init__(self, foo):
+        self.foo = foo
+
+
+def method(foo1, foo):
+    method(foo1, foo1)
+    method(C(1).foo, 2)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.py b/python/testData/refactoring/makeFunctionTopLevel/recursiveMethod.py
new file mode 100644 (file)
index 0000000..908137f
--- /dev/null
@@ -0,0 +1,7 @@
+class C:
+    def __init__(self, foo):
+        self.foo = foo
+    
+    def me<caret>thod(self, foo):
+        self.method(self.foo)
+        C(1).method(2)
diff --git a/python/testData/refactoring/makeFunctionTopLevel/refactoringAvailability.py b/python/testData/refactoring/makeFunctionTopLevel/refactoringAvailability.py
new file mode 100644 (file)
index 0000000..abb2b85
--- /dev/null
@@ -0,0 +1,38 @@
+def func():
+    def local():
+        pass
+
+
+class C:
+    def method(self):
+        pass
+
+    @staticmethod
+    def static_method(x):
+        pass
+
+    @classmethod
+    def class_method(self):
+        pass
+
+    @property
+    def field(self):
+        return self._x
+
+    def __magic__(self):
+        pass
+
+
+class Base:
+    def overridden_method(self):
+        pass
+
+
+class Subclass(Base):
+    def overridden_method(self):
+        super(Subclass, self).overridden_method()
+        
+
+class MyString(str):
+    def upper(self):
+        pass
index f17fcc9256134024b77f934bfef4d66ff578ef84..347b9d2a6621ae62e9d3097d0e18fbaf117ef606 100644 (file)
@@ -79,4 +79,8 @@ public class PyMakeFunctionFromMethodQuickFixTest extends PyQuickFixTestCase {
   public void testLocalClass() {
     doQuickFixTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function"));
   }
+
+  public void testRemoveQualifiers() {
+    doQuickFixTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function"));
+  }
 }
index 7b2958e06ca9f506c64c722a15dd5e92e2c3003b..9027cf4316ecd6e31bb9f37182eb27d3d546158f 100644 (file)
@@ -4,7 +4,7 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.testFramework.PlatformTestUtil;
 import com.jetbrains.python.fixtures.PyTestCase;
 import com.jetbrains.python.psi.PyFile;
-import com.jetbrains.python.refactoring.convert.PyConvertModuleToPackageAction;
+import com.jetbrains.python.refactoring.convertModulePackage.PyConvertModuleToPackageAction;
 
 /**
  * @author Mikhail Golubev
index c98fc785a252af91716df5c3278563a7018b0343..05d597139e4a5dde17d9ca2107880c5810c1cdee 100644 (file)
@@ -5,7 +5,7 @@ import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiManager;
 import com.intellij.testFramework.PlatformTestUtil;
 import com.jetbrains.python.fixtures.PyTestCase;
-import com.jetbrains.python.refactoring.convert.PyConvertPackageToModuleAction;
+import com.jetbrains.python.refactoring.convertModulePackage.PyConvertPackageToModuleAction;
 
 /**
  * @author Mikhail Golubev
diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyMakeFunctionTopLevelTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyMakeFunctionTopLevelTest.java
new file mode 100644 (file)
index 0000000..6e8c3a8
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+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.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 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) {
+    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());
+        }
+        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 doTestSuccess() {
+    doTest(true, null);
+  }
+
+  private void doTestFailure(@NotNull String message) {
+    doTest(true, message);
+  }
+
+  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() {
+    doTestSuccess();
+  }
+
+  // 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());
+  }
+
+  // PY-6637
+  public void testLocalFunctionNonlocalReferenceToOuterScope() {
+    runWithLanguageLevel(LanguageLevel.PYTHON30, new Runnable() {
+      @Override
+      public void run() {
+        doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.nonlocal.writes"));
+      }
+    });
+  }
+
+  // PY-6637
+  public void testLocalFunctionNonlocalReferencesInInnerFunction() {
+    runWithLanguageLevel(LanguageLevel.PYTHON30, new Runnable() {
+      @Override
+      public void run() {
+        doTestSuccess();
+      }
+    });
+  }
+
+  // PY-6637
+  public void testLocalFunctionReferenceToSelf() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.self.reads"));
+  }
+
+  public void testMethodNonlocalReferenceToOuterScope() {
+    runWithLanguageLevel(LanguageLevel.PYTHON30, new Runnable() {
+      @Override
+      public void run() {
+        doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.nonlocal.writes"));
+      }
+    });
+  }
+
+  public void testMethodOuterScopeReads() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.outer.scope.reads"));
+  }
+
+  public void testMethodOtherMethodCalls() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.method.calls"));
+  }
+
+  public void testMethodAttributeWrites() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.attribute.writes"));
+  }
+
+  public void testMethodReadPrivateAttributes() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.private.attributes"));
+  }
+
+  public void testMethodSelfUsedAsOperand() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.special.usage.of.self"));
+  }
+
+  public void testMethodOverriddenSelf() {
+    doTestFailure(PyBundle.message("refactoring.make.function.top.level.error.special.usage.of.self"));
+  }
+
+  public void testMethodSingleAttributeRead() {
+    doTestSuccess();
+  }
+
+  public void testMethodMultipleAttributesReadReferenceQualifier() {
+    doTestSuccess();
+  }
+
+  public void testMethodMultipleAttributesConstructorQualifier() {
+    doTestSuccess();
+  }
+
+  public void testMethodImportUpdates() throws IOException {
+    doMultiFileTest();
+  }
+
+  public void testMethodCalledViaClass() {
+    doTestSuccess();
+  }
+
+  public void testMethodUniqueNameOfExtractedQualifier() {
+    doTestSuccess();
+  }
+
+  public void testMethodUniqueParamNames() {
+    doTestSuccess();
+  }
+
+  public void testRecursiveMethod() {
+    doTestSuccess();
+  }
+
+  public void testRecursiveLocalFunction() {
+    doTestSuccess();
+  }
+
+  public void testMethodNoNewParams() {
+    doTestSuccess();
+  }
+
+  @Override
+  protected String getTestDataPath() {
+    return super.getTestDataPath() + "/refactoring/makeFunctionTopLevel/";
+  }
+}