blob: fa2b4cf1204b48b70a992cfbdd0c2a39df542a9a [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.PsiEquivalenceUtil;
import com.intellij.find.findUsages.FindUsagesHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: Alexey.Ivanov
* Date: Aug 20, 2009
* Time: 7:07:02 PM
*/
public class PyRefactoringUtil {
@NotNull
public static List<PsiElement> getOccurrences(@NotNull final PsiElement pattern, @Nullable final PsiElement context) {
if (context == null) {
return Collections.emptyList();
}
final List<PsiElement> occurrences = new ArrayList<PsiElement>();
final PyElementVisitor visitor = new PyElementVisitor() {
public void visitElement(@NotNull final PsiElement element) {
if (element instanceof PyParameter) {
return;
}
if (PsiEquivalenceUtil.areElementsEquivalent(element, pattern)) {
occurrences.add(element);
return;
}
if (element instanceof PyStringLiteralExpression) {
final Pair<PsiElement, TextRange> selection = pattern.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
if (selection != null) {
final String substring = selection.getSecond().substring(pattern.getText());
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
final String text = element.getText();
if (text != null && expr.getStringNodes().size() == 1) {
final int start = text.indexOf(substring);
if (start >= 0) {
element.putUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE, Pair.create(element, TextRange.from(start, substring.length())));
occurrences.add(element);
return;
}
}
}
}
element.acceptChildren(this);
}
};
context.acceptChildren(visitor);
return occurrences;
}
@Nullable
public static PyExpression getSelectedExpression(@NotNull final Project project,
@NotNull PsiFile file,
@NotNull final PsiElement element1,
@NotNull final PsiElement element2) {
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
if (parent != null && !(parent instanceof PyElement)) {
parent = PsiTreeUtil.getParentOfType(parent, PyElement.class);
}
if (parent == null) {
return null;
}
// If it is PyIfPart for example, parent if statement, we should deny
if (!(parent instanceof PyExpression)){
return null;
}
// We cannot extract anything within import statements
if (PsiTreeUtil.getParentOfType(parent, PyImportStatement.class, PyFromImportStatement.class) != null){
return null;
}
if ((element1 == PsiTreeUtil.getDeepestFirst(parent)) && (element2 == PsiTreeUtil.getDeepestLast(parent))) {
return (PyExpression) parent;
}
// Check if selection breaks AST node in binary expression
if (parent instanceof PyBinaryExpression) {
final String selection = file.getText().substring(element1.getTextOffset(), element2.getTextOffset() + element2.getTextLength());
final PyElementGenerator generator = PyElementGenerator.getInstance(project);
final LanguageLevel langLevel = LanguageLevel.forElement(element1);
final PyExpression expression = generator.createFromText(langLevel, PyAssignmentStatement.class, "z=" + selection).getAssignedValue();
if (!(expression instanceof PyBinaryExpression) || PsiUtilCore.hasErrorElementChild(expression)) {
return null;
}
final String parentText = parent.getText();
final int startOffset = element1.getTextOffset() - parent.getTextOffset() - 1;
if (startOffset < 0) {
return null;
}
final int endOffset = element2.getTextOffset() + element2.getTextLength() - parent.getTextOffset();
final String prefix = parentText.substring(0, startOffset);
final String suffix = parentText.substring(endOffset, parentText.length());
final TextRange textRange = TextRange.from(startOffset, endOffset - startOffset);
final PsiElement fakeExpression = generator.createFromText(langLevel, parent.getClass(), prefix + "python" + suffix);
if (PsiUtilCore.hasErrorElementChild(fakeExpression)) {
return null;
}
expression.putUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE, Pair.create(parent, textRange));
return expression;
}
return null;
}
@NotNull
public static Collection<String> collectUsedNames(@Nullable final PsiElement scope) {
if (!(scope instanceof PyClass) && !(scope instanceof PyFile) && !(scope instanceof PyFunction)) {
return Collections.emptyList();
}
final Set<String> variables = new HashSet<String>() {
@Override
public boolean add(String s) {
return s != null && super.add(s);
}
};
scope.acceptChildren(new PyRecursiveElementVisitor() {
@Override
public void visitPyTargetExpression(@NotNull final PyTargetExpression node) {
variables.add(node.getName());
}
@Override
public void visitPyNamedParameter(@NotNull final PyNamedParameter node) {
variables.add(node.getName());
}
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
variables.add(node.getReferencedName());
}
@Override
public void visitPyFunction(@NotNull final PyFunction node) {
variables.add(node.getName());
}
@Override
public void visitPyClass(@NotNull final PyClass node) {
variables.add(node.getName());
}
});
return variables;
}
@Nullable
public static PsiElement findExpressionInRange(@NotNull final PsiFile file, int startOffset, int endOffset) {
PsiElement element1 = file.findElementAt(startOffset);
PsiElement element2 = file.findElementAt(endOffset - 1);
if (element1 instanceof PsiWhiteSpace) {
startOffset = element1.getTextRange().getEndOffset();
element1 = file.findElementAt(startOffset);
}
if (element2 instanceof PsiWhiteSpace) {
endOffset = element2.getTextRange().getStartOffset();
element2 = file.findElementAt(endOffset - 1);
}
if (element1 == null || element2 == null) {
return null;
}
return getSelectedExpression(file.getProject(), file, element1, element2);
}
@NotNull
public static PsiElement[] findStatementsInRange(@NotNull final PsiFile file, int startOffset, int endOffset) {
PsiElement element1 = file.findElementAt(startOffset);
PsiElement element2 = file.findElementAt(endOffset - 1);
if (element1 instanceof PsiWhiteSpace) {
startOffset = element1.getTextRange().getEndOffset();
element1 = file.findElementAt(startOffset);
}
if (element2 instanceof PsiWhiteSpace) {
endOffset = element2.getTextRange().getStartOffset();
element2 = file.findElementAt(endOffset - 1);
}
if (element1 == null || element2 == null) {
return PsiElement.EMPTY_ARRAY;
}
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
if (parent == null) {
return PsiElement.EMPTY_ARRAY;
}
while (true) {
if (parent instanceof PyStatement) {
parent = parent.getParent();
break;
}
if (parent instanceof PyStatementList) {
break;
}
if (parent == null || parent instanceof PsiFile) {
return PsiElement.EMPTY_ARRAY;
}
parent = parent.getParent();
}
if (!parent.equals(element1)) {
while (!parent.equals(element1.getParent())) {
element1 = element1.getParent();
}
}
if (startOffset != element1.getTextRange().getStartOffset()) {
return PsiElement.EMPTY_ARRAY;
}
if (!parent.equals(element2)) {
while (!parent.equals(element2.getParent())) {
element2 = element2.getParent();
}
}
if (endOffset != element2.getTextRange().getEndOffset()) {
return PsiElement.EMPTY_ARRAY;
}
if (element1 instanceof PyFunction || element1 instanceof PyClass) {
return PsiElement.EMPTY_ARRAY;
}
if (element2 instanceof PyFunction || element2 instanceof PyClass) {
return PsiElement.EMPTY_ARRAY;
}
PsiElement[] children = parent.getChildren();
ArrayList<PsiElement> array = new ArrayList<PsiElement>();
boolean flag = false;
for (PsiElement child : children) {
if (child.equals(element1)) {
flag = true;
}
if (flag && !(child instanceof PsiWhiteSpace)) {
array.add(child);
}
if (child.equals(element2)) {
break;
}
}
for (PsiElement element : array) {
if (!(element instanceof PyStatement || element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
return PsiElement.EMPTY_ARRAY;
}
}
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();
final String firstName = pyFunction.getName();
final String secondName = pyFunction1.getName();
return Comparing.strEqual(firstName, secondName) && firstParams.length == secondParams.length;
}
@NotNull
public static List<UsageInfo> findUsages(@NotNull PsiNamedElement element, boolean forHighlightUsages) {
final List<UsageInfo> usages = new ArrayList<UsageInfo>();
final FindUsagesHandler handler = new PyFindUsagesHandlerFactory().createFindUsagesHandler(element, forHighlightUsages);
assert handler != null;
final List<PsiElement> elementsToProcess = new ArrayList<PsiElement>();
elementsToProcess.addAll(Arrays.asList(handler.getPrimaryElements()));
elementsToProcess.addAll(Arrays.asList(handler.getSecondaryElements()));
for (PsiElement e : elementsToProcess) {
handler.processElementUsages(e, new Processor<UsageInfo>() {
@Override
public boolean process(UsageInfo usageInfo) {
if (!usageInfo.isNonCodeUsage) {
usages.add(usageInfo);
}
return true;
}
}, FindUsagesHandler.createFindUsagesOptions(element.getProject(), null));
}
return usages;
}
}