Add PyElementGenerator#createParameterList and PyElementGenerator#createParameterList
[idea/community.git] / python / src / com / jetbrains / python / refactoring / PyRefactoringUtil.java
1 /*
2  * Copyright 2000-2014 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;
17
18 import com.intellij.codeInsight.PsiEquivalenceUtil;
19 import com.intellij.find.findUsages.FindUsagesHandler;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Comparing;
22 import com.intellij.openapi.util.Pair;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.psi.*;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.psi.util.PsiUtilCore;
27 import com.intellij.usageView.UsageInfo;
28 import com.intellij.util.Processor;
29 import com.intellij.util.containers.HashSet;
30 import com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory;
31 import com.jetbrains.python.psi.*;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.util.*;
36
37 /**
38  * Created by IntelliJ IDEA.
39  * User: Alexey.Ivanov
40  * Date: Aug 20, 2009
41  * Time: 7:07:02 PM
42  */
43 public class PyRefactoringUtil {
44   private PyRefactoringUtil() {
45   }
46
47   @NotNull
48   public static List<PsiElement> getOccurrences(@NotNull final PsiElement pattern, @Nullable final PsiElement context) {
49     if (context == null) {
50       return Collections.emptyList();
51     }
52     final List<PsiElement> occurrences = new ArrayList<PsiElement>();
53     final PyElementVisitor visitor = new PyElementVisitor() {
54       public void visitElement(@NotNull final PsiElement element) {
55         if (element instanceof PyParameter) {
56           return;
57         }
58         if (PsiEquivalenceUtil.areElementsEquivalent(element, pattern)) {
59           occurrences.add(element);
60           return;
61         }
62         if (element instanceof PyStringLiteralExpression) {
63           final Pair<PsiElement, TextRange> selection = pattern.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
64           if (selection != null) {
65             final String substring = selection.getSecond().substring(pattern.getText());
66             final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
67             final String text = element.getText();
68             if (text != null && expr.getStringNodes().size() == 1) {
69               final int start = text.indexOf(substring);
70               if (start >= 0) {
71                 element.putUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE, Pair.create(element, TextRange.from(start, substring.length())));
72                 occurrences.add(element);
73                 return;
74               }
75             }
76           }
77         }
78         element.acceptChildren(this);
79       }
80     };
81     context.acceptChildren(visitor);
82     return occurrences;
83   }
84
85   @Nullable
86   public static PyExpression getSelectedExpression(@NotNull final Project project,
87                                                    @NotNull PsiFile file,
88                                                    @NotNull final PsiElement element1,
89                                                    @NotNull final PsiElement element2) {
90     PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
91     if (parent != null && !(parent instanceof PyElement)) {
92       parent = PsiTreeUtil.getParentOfType(parent, PyElement.class);
93     }
94     if (parent == null) {
95       return null;
96     }
97     // If it is PyIfPart for example, parent if statement, we should deny
98     if (!(parent instanceof PyExpression)){
99       return null;
100     }
101     // We cannot extract anything within import statements
102     if (PsiTreeUtil.getParentOfType(parent, PyImportStatement.class, PyFromImportStatement.class) != null){
103       return null;
104     }
105     if ((element1 == PsiTreeUtil.getDeepestFirst(parent)) && (element2 == PsiTreeUtil.getDeepestLast(parent))) {
106       return (PyExpression) parent;
107     }
108
109     // Check if selection breaks AST node in binary expression
110     if (parent instanceof PyBinaryExpression) {
111       final String selection = file.getText().substring(element1.getTextOffset(), element2.getTextOffset() + element2.getTextLength());
112       final PyElementGenerator generator = PyElementGenerator.getInstance(project);
113       final LanguageLevel langLevel = LanguageLevel.forElement(element1);
114       final PyExpression expression = generator.createFromText(langLevel, PyAssignmentStatement.class, "z=" + selection).getAssignedValue();
115       if (!(expression instanceof PyBinaryExpression) || PsiUtilCore.hasErrorElementChild(expression)) {
116         return null;
117       }
118       final String parentText = parent.getText();
119       final int startOffset = element1.getTextOffset() - parent.getTextOffset() - 1;
120       if (startOffset < 0) {
121         return null;
122       }
123       final int endOffset = element2.getTextOffset() + element2.getTextLength() - parent.getTextOffset();
124
125       final String prefix = parentText.substring(0, startOffset);
126       final String suffix = parentText.substring(endOffset, parentText.length());
127       final TextRange textRange = TextRange.from(startOffset, endOffset - startOffset);
128       final PsiElement fakeExpression = generator.createFromText(langLevel, parent.getClass(), prefix + "python" + suffix);
129       if (PsiUtilCore.hasErrorElementChild(fakeExpression)) {
130         return null;
131       }
132
133       expression.putUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE, Pair.create(parent, textRange));
134       return expression;
135     }
136     return null;
137   }
138
139   @NotNull
140   public static Collection<String> collectUsedNames(@Nullable final PsiElement scope) {
141     if (!(scope instanceof PyClass) && !(scope instanceof PyFile) && !(scope instanceof PyFunction)) {
142       return Collections.emptyList();
143     }
144     final Set<String> variables = new HashSet<String>() {
145       @Override
146       public boolean add(String s) {
147         return s != null && super.add(s);
148       }
149     };
150     scope.acceptChildren(new PyRecursiveElementVisitor() {
151       @Override
152       public void visitPyTargetExpression(@NotNull final PyTargetExpression node) {
153         variables.add(node.getName());
154       }
155
156       @Override
157       public void visitPyNamedParameter(@NotNull final PyNamedParameter node) {
158         variables.add(node.getName());
159       }
160
161       @Override
162       public void visitPyReferenceExpression(PyReferenceExpression node) {
163         if (!node.isQualified()) {
164           variables.add(node.getReferencedName());
165         }
166         else {
167           super.visitPyReferenceExpression(node);
168         }
169       }
170
171       @Override
172       public void visitPyFunction(@NotNull final PyFunction node) {
173         variables.add(node.getName());
174       }
175
176       @Override
177       public void visitPyClass(@NotNull final PyClass node) {
178         variables.add(node.getName());
179       }
180     });
181     return variables;
182   }
183
184   @Nullable
185   public static PsiElement findExpressionInRange(@NotNull final PsiFile file, int startOffset, int endOffset) {
186     PsiElement element1 = file.findElementAt(startOffset);
187     PsiElement element2 = file.findElementAt(endOffset - 1);
188     if (element1 instanceof PsiWhiteSpace) {
189       startOffset = element1.getTextRange().getEndOffset();
190       element1 = file.findElementAt(startOffset);
191     }
192     if (element2 instanceof PsiWhiteSpace) {
193       endOffset = element2.getTextRange().getStartOffset();
194       element2 = file.findElementAt(endOffset - 1);
195     }
196     if (element1 == null || element2 == null) {
197       return null;
198     }
199     return getSelectedExpression(file.getProject(), file, element1, element2);
200   }
201
202   @NotNull
203   public static PsiElement[] findStatementsInRange(@NotNull final PsiFile file, int startOffset, int endOffset) {
204     PsiElement element1 = file.findElementAt(startOffset);
205     PsiElement element2 = file.findElementAt(endOffset - 1);
206     if (element1 instanceof PsiWhiteSpace) {
207       startOffset = element1.getTextRange().getEndOffset();
208       element1 = file.findElementAt(startOffset);
209     }
210     if (element2 instanceof PsiWhiteSpace) {
211       endOffset = element2.getTextRange().getStartOffset();
212       element2 = file.findElementAt(endOffset - 1);
213     }
214     if (element1 == null || element2 == null) {
215       return PsiElement.EMPTY_ARRAY;
216     }
217
218     PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
219     if (parent == null) {
220       return PsiElement.EMPTY_ARRAY;
221     }
222
223     while (true) {
224       if (parent instanceof PyStatement) {
225         parent = parent.getParent();
226         break;
227       }
228       if (parent instanceof PyStatementList) {
229         break;
230       }
231       if (parent == null || parent instanceof PsiFile) {
232         return PsiElement.EMPTY_ARRAY;
233       }
234       parent = parent.getParent();
235     }
236
237     if (!parent.equals(element1)) {
238       while (!parent.equals(element1.getParent())) {
239         element1 = element1.getParent();
240       }
241     }
242     if (startOffset != element1.getTextRange().getStartOffset()) {
243       return PsiElement.EMPTY_ARRAY;
244     }
245
246     if (!parent.equals(element2)) {
247       while (!parent.equals(element2.getParent())) {
248         element2 = element2.getParent();
249       }
250     }
251     if (endOffset != element2.getTextRange().getEndOffset()) {
252       return PsiElement.EMPTY_ARRAY;
253     }
254
255     if (element1 instanceof PyFunction || element1 instanceof PyClass) {
256       return PsiElement.EMPTY_ARRAY;
257     }
258     if (element2 instanceof PyFunction || element2 instanceof PyClass) {
259       return PsiElement.EMPTY_ARRAY;
260     }
261
262     PsiElement[] children = parent.getChildren();
263     ArrayList<PsiElement> array = new ArrayList<PsiElement>();
264     boolean flag = false;
265     for (PsiElement child : children) {
266       if (child.equals(element1)) {
267         flag = true;
268       }
269       if (flag && !(child instanceof PsiWhiteSpace)) {
270         array.add(child);
271       }
272       if (child.equals(element2)) {
273         break;
274       }
275     }
276
277     for (PsiElement element : array) {
278       if (!(element instanceof PyStatement || element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
279         return PsiElement.EMPTY_ARRAY;
280       }
281     }
282     return PsiUtilCore.toPsiElementArray(array);
283   }
284
285   public static boolean areConflictingMethods(PyFunction pyFunction, PyFunction pyFunction1) {
286     final PyParameter[] firstParams = pyFunction.getParameterList().getParameters();
287     final PyParameter[] secondParams = pyFunction1.getParameterList().getParameters();
288     final String firstName = pyFunction.getName();
289     final String secondName = pyFunction1.getName();
290
291     return Comparing.strEqual(firstName, secondName) && firstParams.length == secondParams.length;
292   }
293
294   @NotNull
295   public static List<UsageInfo> findUsages(@NotNull PsiNamedElement element, boolean forHighlightUsages) {
296     final List<UsageInfo> usages = new ArrayList<UsageInfo>();
297     final FindUsagesHandler handler = new PyFindUsagesHandlerFactory().createFindUsagesHandler(element, forHighlightUsages);
298     assert handler != null;
299     final List<PsiElement> elementsToProcess = new ArrayList<PsiElement>();
300     Collections.addAll(elementsToProcess, handler.getPrimaryElements());
301     Collections.addAll(elementsToProcess, handler.getSecondaryElements());
302     for (PsiElement e : elementsToProcess) {
303       handler.processElementUsages(e, new Processor<UsageInfo>() {
304         @Override
305         public boolean process(UsageInfo usageInfo) {
306           if (!usageInfo.isNonCodeUsage) {
307             usages.add(usageInfo);
308           }
309           return true;
310         }
311       }, FindUsagesHandler.createFindUsagesOptions(element.getProject(), null));
312     }
313     return usages;
314   }
315 }