PY-6637 Add test cases on detection of nonlocal references inside escalated function
[idea/community.git] / python / src / com / jetbrains / python / refactoring / convertTopLevelFunction / PyConvertLocalFunctionToTopLevelFunctionAction.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.refactoring.convertTopLevelFunction;
17
18 import com.intellij.openapi.actionSystem.CommonDataKeys;
19 import com.intellij.openapi.actionSystem.DataContext;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.editor.Editor;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.PsiFile;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.refactoring.RefactoringActionHandler;
27 import com.intellij.refactoring.RefactoringBundle;
28 import com.intellij.refactoring.util.CommonRefactoringUtil;
29 import com.intellij.util.IncorrectOperationException;
30 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
31 import com.jetbrains.python.psi.PyFunction;
32 import com.jetbrains.python.psi.PyReferenceExpression;
33 import com.jetbrains.python.refactoring.PyBaseRefactoringAction;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 /**
38  * @author Mikhail Golubev
39  */
40 public class PyConvertLocalFunctionToTopLevelFunctionAction extends PyBaseRefactoringAction {
41   public static final String ID = "py.make.function.top.level";
42
43   @Override
44   protected boolean isAvailableInEditorOnly() {
45     return true;
46   }
47
48   @Override
49   protected boolean isEnabledOnElementInsideEditor(@NotNull PsiElement element,
50                                                    @NotNull Editor editor,
51                                                    @NotNull PsiFile file,
52                                                    @NotNull DataContext context) {
53     return findTargetFunction(element) != null;
54   }
55
56   @Override
57   protected boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements) {
58     return false;
59   }
60
61   @Nullable
62   private static PyFunction findTargetFunction(@NotNull PsiElement element) {
63     PyFunction result = null;
64     if (isLocalFunction(element)) {
65       result = (PyFunction)element;
66     }
67     else {
68       final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
69       if (refExpr == null) {
70         return null;
71       }
72       final PsiElement resolved = refExpr.getReference().resolve();
73       if (isLocalFunction(resolved)) {
74         result = (PyFunction)resolved;
75       }
76     }
77     return result;
78   }
79
80   private static boolean isLocalFunction(@Nullable PsiElement resolved) {
81     if (resolved instanceof PyFunction && PsiTreeUtil.getParentOfType(resolved, ScopeOwner.class, true) instanceof PyFunction) {
82       return true;
83     }
84     return false;
85   }
86
87   @Nullable
88   @Override
89   protected RefactoringActionHandler getHandler(@NotNull DataContext dataContext) {
90     return new RefactoringActionHandler() {
91       @Override
92       public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
93         final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
94         if (element != null) {
95           final PyFunction function = findTargetFunction(element);
96           if (function != null) {
97             try {
98               new PyMakeFunctionTopLevelProcessor(function, editor).run();
99             }
100             catch (IncorrectOperationException e) {
101               if (ApplicationManager.getApplication().isUnitTestMode()) {
102                 throw e;
103               }
104               CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), ID, project);
105             }
106           }
107         }
108       }
109
110       @Override
111       public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
112         // should be called only from the editor
113       }
114     };
115   }
116 }