blob: a967467ed3af5965dc08a5a05de2d83de2017130 [file] [log] [blame]
/*
* Copyright 2000-2014 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.psi;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.lang.ASTFactory;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.*;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
import com.jetbrains.python.magicLiteral.PyMagicLiteralTools;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.stubs.PySetuptoolsNamespaceIndex;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.refactoring.classes.PyDependenciesComparator;
import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.*;
import java.util.List;
import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
public class PyUtil {
private PyUtil() {
}
public static ASTNode getNextNonWhitespace(ASTNode after) {
ASTNode node = after;
do {
node = node.getTreeNext();
}
while (isWhitespace(node));
return node;
}
public static ASTNode getPreviousNonWhitespace(ASTNode after) {
ASTNode node = after;
do {
node = node.getTreePrev();
}
while (isWhitespace(node));
return node;
}
private static boolean isWhitespace(ASTNode node) {
return node != null && node.getElementType().equals(TokenType.WHITE_SPACE);
}
@Nullable
public static PsiElement getFirstNonCommentAfter(PsiElement start) {
PsiElement seeker = start;
while (seeker instanceof PsiWhiteSpace || seeker instanceof PsiComment) seeker = seeker.getNextSibling();
return seeker;
}
@Nullable
public static PsiElement getFirstNonCommentBefore(PsiElement start) {
PsiElement seeker = start;
while (seeker instanceof PsiWhiteSpace || seeker instanceof PsiComment) {
seeker = seeker.getPrevSibling();
}
return seeker;
}
@NotNull
public static <T extends PyElement> T[] getAllChildrenOfType(@NotNull PsiElement element, @NotNull Class<T> aClass) {
List<T> result = new SmartList<T>();
for (PsiElement child : element.getChildren()) {
if (instanceOf(child, aClass)) {
//noinspection unchecked
result.add((T)child);
}
else {
ContainerUtil.addAll(result, getAllChildrenOfType(child, aClass));
}
}
return ArrayUtil.toObjectArray(result, aClass);
}
/**
* @see PyUtil#flattenedParensAndTuples
*/
protected static List<PyExpression> _unfoldParenExprs(PyExpression[] targets, List<PyExpression> receiver,
boolean unfoldListLiterals, boolean unfoldStarExpressions) {
// NOTE: this proliferation of instanceofs is not very beautiful. Maybe rewrite using a visitor.
for (PyExpression exp : targets) {
if (exp instanceof PyParenthesizedExpression) {
final PyParenthesizedExpression parex = (PyParenthesizedExpression)exp;
_unfoldParenExprs(new PyExpression[]{parex.getContainedExpression()}, receiver, unfoldListLiterals, unfoldStarExpressions);
}
else if (exp instanceof PyTupleExpression) {
final PyTupleExpression tupex = (PyTupleExpression)exp;
_unfoldParenExprs(tupex.getElements(), receiver, unfoldListLiterals, unfoldStarExpressions);
}
else if (exp instanceof PyListLiteralExpression && unfoldListLiterals) {
final PyListLiteralExpression listLiteral = (PyListLiteralExpression)exp;
_unfoldParenExprs(listLiteral.getElements(), receiver, unfoldListLiterals, unfoldStarExpressions);
}
else if (exp instanceof PyStarExpression && unfoldStarExpressions) {
_unfoldParenExprs(new PyExpression[]{((PyStarExpression)exp).getExpression()}, receiver, unfoldListLiterals, unfoldStarExpressions);
}
else if (exp != null) {
receiver.add(exp);
}
}
return receiver;
}
// Poor man's catamorhpism :)
/**
* Flattens the representation of every element in targets, and puts all results together.
* Elements of every tuple nested in target item are brought to the top level: (a, (b, (c, d))) -> (a, b, c, d)
* Typical usage: <code>flattenedParensAndTuples(some_tuple.getExpressions())</code>.
*
* @param targets target elements.
* @return the list of flattened expressions.
*/
@NotNull
public static List<PyExpression> flattenedParensAndTuples(PyExpression... targets) {
return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), false, false);
}
@NotNull
public static List<PyExpression> flattenedParensAndLists(PyExpression... targets) {
return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), true, true);
}
@NotNull
public static List<PyExpression> flattenedParensAndStars(PyExpression... targets) {
return _unfoldParenExprs(targets, new ArrayList<PyExpression>(targets.length), false, true);
}
// Poor man's filter
// TODO: move to a saner place
public static boolean instanceOf(Object obj, Class... possibleClasses) {
if (obj == null || possibleClasses == null) return false;
for (Class cls : possibleClasses) {
if (cls.isInstance(obj)) return true;
}
return false;
}
/**
* Produce a reasonable representation of a PSI element, good for debugging.
*
* @param elt element to represent; nulls and invalid nodes are ok.
* @param cutAtEOL if true, representation stops at nearest EOL inside the element.
* @return the representation.
*/
@NotNull
@NonNls
public static String getReadableRepr(PsiElement elt, final boolean cutAtEOL) {
if (elt == null) return "null!";
ASTNode node = elt.getNode();
if (node == null) {
return "null";
}
else {
String s = node.getText();
int cut_pos;
if (cutAtEOL) {
cut_pos = s.indexOf('\n');
}
else {
cut_pos = -1;
}
if (cut_pos < 0) cut_pos = s.length();
return s.substring(0, Math.min(cut_pos, s.length()));
}
}
@Nullable
public static PyClass getContainingClassOrSelf(final PsiElement element) {
PsiElement current = element;
while (current != null && !(current instanceof PyClass)) {
current = current.getParent();
}
return (PyClass)current;
}
/**
* @param element for which to obtain the file
* @return PyFile, or null, if there's no containing file, or it is not a PyFile.
*/
@Nullable
public static PyFile getContainingPyFile(PyElement element) {
final PsiFile containingFile = element.getContainingFile();
return containingFile instanceof PyFile ? (PyFile)containingFile : null;
}
/**
* Shows an information balloon in a reasonable place at the top right of the window.
*
* @param project our project
* @param message the text, HTML markup allowed
* @param messageType message type, changes the icon and the background.
*/
// TODO: move to a better place
public static void showBalloon(Project project, String message, MessageType messageType) {
// ripped from com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier
final JFrame frame = WindowManager.getInstance().getFrame(project.isDefault() ? null : project);
if (frame == null) return;
final JComponent component = frame.getRootPane();
if (component == null) return;
final Rectangle rect = component.getVisibleRect();
final Point p = new Point(rect.x + rect.width - 10, rect.y + 10);
final RelativePoint point = new RelativePoint(component, p);
JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(message, messageType.getDefaultIcon(), messageType.getPopupBackground(), null)
.setShowCallout(false).setCloseButtonEnabled(true)
.createBalloon().show(point, Balloon.Position.atLeft);
}
@NonNls
/**
* Returns a quoted string representation, or "null".
*/
public static String nvl(Object s) {
if (s != null) {
return "'" + s.toString() + "'";
}
else {
return "null";
}
}
/**
* Adds an item into a comma-separated list in a PSI tree. E.g. can turn "foo, bar" into "foo, bar, baz", adding commas as needed.
*
* @param parent the element to represent the list; we're adding a child to it.
* @param newItem the element we're inserting (the "baz" in the example).
* @param beforeThis node to mark the insertion point inside the list; must belong to a child of target. Set to null to add first element.
* @param isFirst true if we don't need a comma before the element we're adding.
* @param isLast true if we don't need a comma after the element we're adding.
*/
public static void addListNode(PsiElement parent, PsiElement newItem, ASTNode beforeThis,
boolean isFirst, boolean isLast, boolean addWhitespace) {
if (!FileModificationService.getInstance().preparePsiElementForWrite(parent)) {
return;
}
ASTNode node = parent.getNode();
assert node != null;
ASTNode itemNode = newItem.getNode();
assert itemNode != null;
Project project = parent.getProject();
PyElementGenerator gen = PyElementGenerator.getInstance(project);
if (!isFirst) node.addChild(gen.createComma(), beforeThis);
node.addChild(itemNode, beforeThis);
if (!isLast) node.addChild(gen.createComma(), beforeThis);
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.
*/
@NotNull
public static List<PyClass> getAllSuperClasses(@NotNull PyClass pyClass) {
List<PyClass> superClasses = new ArrayList<PyClass>();
for (PyClass ancestor : pyClass.getAncestorClasses()) {
if (!PyNames.FAKE_OLD_BASE.equals(ancestor.getName())) {
superClasses.add(ancestor);
}
}
return superClasses;
}
// TODO: move to a more proper place?
/**
* Determine the type of a special attribute. Currently supported: {@code __class__} and {@code __dict__}.
*
* @param ref reference to a possible attribute; only qualified references make sense.
* @return type, or null (if type cannot be determined, reference is not to a known attribute, etc.)
*/
@Nullable
public static PyType getSpecialAttributeType(@Nullable PyReferenceExpression ref, TypeEvalContext context) {
if (ref != null) {
PyExpression qualifier = ref.getQualifier();
if (qualifier != null) {
String attr_name = ref.getReferencedName();
if (PyNames.__CLASS__.equals(attr_name)) {
PyType qualifierType = context.getType(qualifier);
if (qualifierType instanceof PyClassType) {
return new PyClassTypeImpl(((PyClassType)qualifierType).getPyClass(), true); // always as class, never instance
}
}
else if (PyNames.DICT.equals(attr_name)) {
PyType qualifierType = context.getType(qualifier);
if (qualifierType instanceof PyClassType && ((PyClassType)qualifierType).isDefinition()) {
return PyBuiltinCache.getInstance(ref).getDictType();
}
}
}
}
return null;
}
/**
* Makes sure that 'thing' is not null; else throws an {@link IncorrectOperationException}.
*
* @param thing what we check.
* @return thing, if not null.
*/
@NotNull
public static <T> T sure(T thing) {
if (thing == null) throw new IncorrectOperationException();
return thing;
}
/**
* Makes sure that the 'thing' is true; else throws an {@link IncorrectOperationException}.
*
* @param thing what we check.
*/
public static void sure(boolean thing) {
if (!thing) throw new IncorrectOperationException();
}
public static boolean isAttribute(PyTargetExpression ex) {
return isInstanceAttribute(ex) || isClassAttribute(ex);
}
public static boolean isInstanceAttribute(PyExpression target) {
if (!(target instanceof PyTargetExpression)) {
return false;
}
final ScopeOwner owner = ScopeUtil.getScopeOwner(target);
if (owner instanceof PyFunction) {
final PyFunction method = (PyFunction)owner;
if (method.getContainingClass() != null) {
if (method.getStub() != null) {
return true;
}
final PyParameter[] params = method.getParameterList().getParameters();
if (params.length > 0) {
final PyTargetExpression targetExpr = (PyTargetExpression)target;
final PyExpression qualifier = targetExpr.getQualifier();
return qualifier != null && qualifier.getText().equals(params[0].getName());
}
}
}
return false;
}
public static boolean isClassAttribute(PsiElement element) {
return element instanceof PyTargetExpression && ScopeUtil.getScopeOwner(element) instanceof PyClass;
}
public static boolean isIfNameEqualsMain(PyIfStatement ifStatement) {
final PyExpression condition = ifStatement.getIfPart().getCondition();
return isNameEqualsMain(condition);
}
private static boolean isNameEqualsMain(PyExpression condition) {
if (condition instanceof PyParenthesizedExpression) {
return isNameEqualsMain(((PyParenthesizedExpression)condition).getContainedExpression());
}
if (condition instanceof PyBinaryExpression) {
PyBinaryExpression binaryExpression = (PyBinaryExpression)condition;
if (binaryExpression.getOperator() == PyTokenTypes.OR_KEYWORD) {
return isNameEqualsMain(binaryExpression.getLeftExpression()) || isNameEqualsMain(binaryExpression.getRightExpression());
}
final PyExpression rhs = binaryExpression.getRightExpression();
return binaryExpression.getOperator() == PyTokenTypes.EQEQ &&
binaryExpression.getLeftExpression().getText().equals(PyNames.NAME) &&
rhs != null && rhs.getText().contains("__main__");
}
return false;
}
/**
* Searhes for a method wrapping given element.
*
* @param start element presumably inside a method
* @param deep if true, allow 'start' to be inside functions nested in a method; else, 'start' must be directly inside a method.
* @return if not 'deep', [0] is the method and [1] is the class; if 'deep', first several elements may be the nested functions,
* the last but one is the method, and the last is the class.
*/
@Nullable
public static List<PsiElement> searchForWrappingMethod(PsiElement start, boolean deep) {
PsiElement seeker = start;
List<PsiElement> ret = new ArrayList<PsiElement>(2);
while (seeker != null) {
PyFunction func = PsiTreeUtil.getParentOfType(seeker, PyFunction.class, true, PyClass.class);
if (func != null) {
PyClass cls = func.getContainingClass();
if (cls != null) {
ret.add(func);
ret.add(cls);
return ret;
}
else if (deep) {
ret.add(func);
seeker = func;
}
else {
return null; // no immediate class
}
}
else {
return null; // no function
}
}
return null;
}
public static boolean inSameFile(@NotNull PsiElement e1, @NotNull PsiElement e2) {
final PsiFile f1 = e1.getContainingFile();
final PsiFile f2 = e2.getContainingFile();
if (f1 == null || f2 == null) {
return false;
}
return f1 == f2;
}
public static boolean isTopLevel(@NotNull PsiElement element) {
if (element instanceof StubBasedPsiElement) {
final StubElement stub = ((StubBasedPsiElement)element).getStub();
if (stub != null) {
final StubElement parentStub = stub.getParentStub();
if (parentStub != null) {
return parentStub.getPsi() instanceof PsiFile;
}
}
}
return ScopeUtil.getScopeOwner(element) instanceof PsiFile;
}
public static void deletePycFiles(String pyFilePath) {
if (pyFilePath.endsWith(".py")) {
List<File> filesToDelete = new ArrayList<File>();
File pyc = new File(pyFilePath + "c");
if (pyc.exists()) {
filesToDelete.add(pyc);
}
File pyo = new File(pyFilePath + "o");
if (pyo.exists()) {
filesToDelete.add(pyo);
}
final File file = new File(pyFilePath);
File pycache = new File(file.getParentFile(), PyNames.PYCACHE);
if (pycache.isDirectory()) {
final String shortName = FileUtil.getNameWithoutExtension(file);
Collections.addAll(filesToDelete, pycache.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (!FileUtilRt.extensionEquals(pathname.getName(), "pyc")) return false;
String nameWithMagic = FileUtil.getNameWithoutExtension(pathname);
return FileUtil.getNameWithoutExtension(nameWithMagic).equals(shortName);
}
}));
}
FileUtil.asyncDelete(filesToDelete);
}
}
public static String getElementNameWithoutExtension(PsiNamedElement psiNamedElement) {
return psiNamedElement instanceof PyFile
? FileUtil.getNameWithoutExtension(((PyFile)psiNamedElement).getName())
: psiNamedElement.getName();
}
public static boolean hasUnresolvedAncestors(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
for (PyClassLikeType type : cls.getAncestorTypes(context)) {
if (type == null) {
return true;
}
}
return false;
}
@NotNull
public static AccessDirection getPropertyAccessDirection(@NotNull PyFunction function) {
final Property property = function.getProperty();
if (property != null) {
if (property.getGetter().valueOrNull() == function) {
return AccessDirection.READ;
}
if (property.getSetter().valueOrNull() == function) {
return AccessDirection.WRITE;
}
else if (property.getDeleter().valueOrNull() == function) {
return AccessDirection.DELETE;
}
}
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() + ".");
}
}
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();
}
}
/**
* Returns string that represents element in string search.
*
* @param element element to search
* @return string that represents element
*/
@NotNull
public static String computeElementNameForStringSearch(@NotNull final PsiElement element) {
if (element instanceof PyFile) {
return FileUtil.getNameWithoutExtension(((PyFile)element).getName());
}
if (element instanceof PsiDirectory) {
return ((PsiDirectory)element).getName();
}
// Magic literals are always represented by their string values
if ((element instanceof PyStringLiteralExpression) && PyMagicLiteralTools.isMagicLiteral(element)) {
final String name = ((StringLiteralExpression)element).getStringValue();
if (name != null) {
return name;
}
}
if (element instanceof PyElement) {
final String name = ((PyElement)element).getName();
if (name != null) {
return name;
}
}
return element.getNode().getText();
}
public static boolean isOwnScopeComprehension(@NotNull PyComprehensionElement comprehension) {
final boolean isAtLeast30 = LanguageLevel.forElement(comprehension).isAtLeast(LanguageLevel.PYTHON30);
final boolean isListComprehension = comprehension instanceof PyListCompExpression;
return !isListComprehension || isAtLeast30;
}
public static boolean hasCustomDecorators(@NotNull PyDecoratable decoratable) {
return PyKnownDecoratorUtil.hasNonBuiltinDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
}
public static boolean isDecoratedAsAbstract(@NotNull final PyDecoratable decoratable) {
return PyKnownDecoratorUtil.hasAbstractDecorator(decoratable, TypeEvalContext.codeInsightFallback(null));
}
public static ASTNode createNewName(PyElement element, String name) {
return PyElementGenerator.getInstance(element.getProject()).createNameIdentifier(name, LanguageLevel.forElement(element));
}
/**
* Finds element declaration by resolving its references top the top but not further than file (to prevent unstubing)
*
* @param element element to resolve
* @return its declaration
*/
@NotNull
public static PsiElement resolveToTheTop(@NotNull final PsiElement elementToResolve) {
PsiElement currentElement = elementToResolve;
while (true) {
final PsiReference reference = currentElement.getReference();
if (reference == null) {
break;
}
final PsiElement resolve = reference.resolve();
if ((resolve == null) || resolve.equals(currentElement) || !inSameFile(resolve, currentElement)) {
break;
}
currentElement = resolve;
}
return currentElement;
}
@NotNull
public static List<PsiElement> multiResolveTopPriority(@NotNull PsiElement element, @NotNull PyResolveContext resolveContext) {
if (element instanceof PyReferenceOwner) {
final PsiPolyVariantReference ref = ((PyReferenceOwner)element).getReference(resolveContext);
return filterTopPriorityResults(ref.multiResolve(false));
}
else {
final PsiReference reference = element.getReference();
return reference != null ? Collections.singletonList(reference.resolve()) : Collections.<PsiElement>emptyList();
}
}
@NotNull
public static List<PsiElement> multiResolveTopPriority(@NotNull PsiPolyVariantReference reference) {
return filterTopPriorityResults(reference.multiResolve(false));
}
@NotNull
private static List<PsiElement> filterTopPriorityResults(@NotNull ResolveResult[] resolveResults) {
if (resolveResults.length == 0) {
return Collections.emptyList();
}
final List<PsiElement> filtered = new ArrayList<PsiElement>();
final int maxRate = getMaxRate(resolveResults);
for (ResolveResult resolveResult : resolveResults) {
final int rate = resolveResult instanceof RatedResolveResult ? ((RatedResolveResult)resolveResult).getRate() : 0;
if (rate >= maxRate) {
filtered.add(resolveResult.getElement());
}
}
return filtered;
}
private static int getMaxRate(@NotNull ResolveResult[] resolveResults) {
int maxRate = Integer.MIN_VALUE;
for (ResolveResult resolveResult : resolveResults) {
if (resolveResult instanceof RatedResolveResult) {
final int rate = ((RatedResolveResult)resolveResult).getRate();
if (rate > maxRate) {
maxRate = rate;
}
}
}
return maxRate;
}
/**
* Gets class init method
*
* @param pyClass class where to find init
* @return class init method if any
*/
@Nullable
public static PyFunction getInitMethod(@NotNull final PyClass pyClass) {
return pyClass.findMethodByName(PyNames.INIT, false);
}
/**
* Returns Python language level for a virtual file.
*
* @see {@link LanguageLevel#forElement}
*/
@NotNull
public static LanguageLevel getLanguageLevelForVirtualFile(@NotNull Project project,
@NotNull VirtualFile virtualFile) {
if (virtualFile instanceof VirtualFileWindow) {
virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
}
// Most of the cases should be handled by this one, PyLanguageLevelPusher pushes folders only
final VirtualFile folder = virtualFile.getParent();
if (folder != null) {
LanguageLevel level = folder.getUserData(LanguageLevel.KEY);
if (level == null) level = PythonLanguageLevelPusher.getFileLanguageLevel(project, virtualFile);
if (level != null) return level;
}
else {
// However this allows us to setup language level per file manually
// in case when it is LightVirtualFile
final LanguageLevel level = virtualFile.getUserData(LanguageLevel.KEY);
if (level != null) return level;
if (ApplicationManager.getApplication().isUnitTestMode()) {
final LanguageLevel languageLevel = LanguageLevel.FORCE_LANGUAGE_LEVEL;
if (languageLevel != null) {
return languageLevel;
}
}
}
return guessLanguageLevel(project);
}
private static LanguageLevel guessLanguageLevel(@NotNull Project project) {
final ModuleManager moduleManager = ModuleManager.getInstance(project);
if (moduleManager != null) {
for (Module projectModule : moduleManager.getModules()) {
final Sdk sdk = PythonSdkType.findPythonSdk(projectModule);
if (sdk != null) {
return PythonSdkType.getLanguageLevelForSdk(sdk);
}
}
}
return LanguageLevel.getDefault();
}
/**
* Clone of C# "as" operator.
* Checks if expression has correct type and casts it if it has. Returns null otherwise.
* It saves coder from "instanceof / cast" chains.
*
* @param expression expression to check
* @param clazz class to cast
* @param <T> class to cast
* @return expression casted to appropriate type (if could be casted). Null otherwise.
*/
@Nullable
@SuppressWarnings("unchecked")
public static <T> T as(@Nullable final Object expression, @NotNull final Class<T> clazz) {
if (expression == null) {
return null;
}
if (clazz.isAssignableFrom(expression.getClass())) {
return (T)expression;
}
return null;
}
// TODO: Move to PsiElement?
/**
* Searches for references injected to element with certain type
*
* @param element element to search injected references for
* @param expectedClass expected type of element reference resolved to
* @param <T> expected type of element reference resolved to
* @return resolved element if found or null if not found
*/
@Nullable
public static <T extends PsiElement> T findReference(@NotNull final PsiElement element, @NotNull final Class<T> expectedClass) {
for (final PsiReference reference : element.getReferences()) {
final T result = as(reference.resolve(), expectedClass);
if (result != null) {
return result;
}
}
return null;
}
/**
* Converts collection to list of certain type
*
* @param expression expression of collection type
* @param elementClass expected element type
* @param <T> expected element type
* @return list of elements of expected element type
*/
@NotNull
public static <T> List<T> asList(@Nullable final Collection<?> expression, @NotNull final Class<T> elementClass) {
if ((expression == null) || expression.isEmpty()) {
return Collections.emptyList();
}
final List<T> result = new ArrayList<T>();
for (final Object element : expression) {
final T toAdd = as(element, elementClass);
if (toAdd != null) {
result.add(toAdd);
}
}
return result;
}
public static class KnownDecoratorProviderHolder {
public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME);
private KnownDecoratorProviderHolder() {
}
}
/**
* Returns child element in the psi tree
*
* @param filter Types of expected child
* @param number number
* @param element tree parent node
* @return PsiElement - child psiElement
*/
@Nullable
public static PsiElement getChildByFilter(@NotNull final PsiElement element, final @NotNull TokenSet filter, final int number) {
final ASTNode node = element.getNode();
if (node != null) {
final ASTNode[] children = node.getChildren(filter);
return (0 <= number && number < children.length) ? children[number].getPsi() : null;
}
return null;
}
/**
* Returns first child psi element with specified element type or {@code null} if no such element exists.
* Semantically it's the same as {@code getChildByFilter(element, TokenSet.create(type), 0)}.
*
* @param element tree parent node
* @param type element type expected
* @return child element described
*/
@Nullable
public static PsiElement getFirstChildOfType(@NotNull final PsiElement element, @NotNull PyElementType type) {
final ASTNode child = element.getNode().findChildByType(type);
return child != null ? child.getPsi() : null;
}
/**
* If argument is a PsiDirectory, turn it into a PsiFile that points to __init__.py in that directory.
* If there's no __init__.py there, null is returned, there's no point to resolve to a dir which is not a package.
* Alas, resolve() and multiResolve() can't return anything but a PyFile or PsiFileImpl.isPsiUpToDate() would fail.
* This is because isPsiUpToDate() relies on identity of objects returned by FileViewProvider.getPsi().
* If we ever need to exactly tell a dir from __init__.py, that logic has to change.
*
* @param target a resolve candidate.
* @return a PsiFile if target was a PsiDirectory, or null, or target unchanged.
*/
@Nullable
public static PsiElement turnDirIntoInit(PsiElement target) {
if (target instanceof PsiDirectory) {
final PsiDirectory dir = (PsiDirectory)target;
final PsiFile file = dir.findFile(PyNames.INIT_DOT_PY);
if (file != null) {
return file; // ResolveImportUtil will extract directory part as needed, everyone else are better off with a file.
}
else {
return null;
} // dir without __init__.py does not resolve
}
else {
return target;
} // don't touch non-dirs
}
/**
* If target is a PsiDirectory, that is also a valid Python package, return PsiFile that points to __init__.py,
* if such file exists, or directory itself (i.e. namespace package). Otherwise, return {@code null}.
* Unlike {@link #turnDirIntoInit(com.intellij.psi.PsiElement)} this function handles namespace packages and
* accepts only PsiDirectories as target.
*
* @param target directory to check
* @param anchor optional PSI element to determine language level as for {@link #isPackage(com.intellij.psi.PsiDirectory, com.intellij.psi.PsiElement)}
* @return PsiFile or PsiDirectory, if target is a Python package and {@code null} null otherwise
*/
@Nullable
public static PsiElement turnDirIntoPackageElement(@NotNull PsiDirectory target, @Nullable PsiElement anchor) {
if (isPackage(target, anchor)) {
final PsiFile file = target.findFile(PyNames.INIT_DOT_PY);
return file != null ? file : target;
}
else {
return null;
}
}
/**
* If target is a Python module named __init__.py file, return its directory. Otherwise return target unchanged.
* @param target PSI element to check
* @return PsiDirectory or target unchanged
*/
@Contract("null -> null; !null -> !null")
@Nullable
public static PsiElement turnInitIntoDir(@Nullable PsiElement target) {
if (target instanceof PyFile && isPackage((PsiFile)target)) {
return ((PsiFile)target).getContainingDirectory();
}
return target;
}
public static boolean isPackage(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
if (directory.findFile(PyNames.INIT_DOT_PY) != null) {
return true;
}
final LanguageLevel level = anchor != null ?
LanguageLevel.forElement(anchor) :
getLanguageLevelForVirtualFile(directory.getProject(), directory.getVirtualFile());
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
return true;
}
return isSetuptoolsNamespacePackage(directory);
}
public static boolean isPackage(@NotNull PsiFile file) {
return PyNames.INIT_DOT_PY.equals(file.getName());
}
@Nullable
public static PsiElement getPackageElement(@NotNull PsiDirectory directory, @Nullable PsiElement anchor) {
if (isPackage(directory, anchor)) {
final PsiElement init = turnDirIntoInit(directory);
if (init != null) {
return init;
}
return directory;
}
return null;
}
private static boolean isSetuptoolsNamespacePackage(@NotNull PsiDirectory directory) {
final String packagePath = getPackagePath(directory);
return packagePath != null && !PySetuptoolsNamespaceIndex.find(packagePath, directory.getProject()).isEmpty();
}
@Nullable
private static String getPackagePath(@NotNull PsiDirectory directory) {
final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(directory);
return name != null ? name.toString() : null;
}
/**
* Counts initial underscores of an identifier.
*
* @param name identifier
* @return 0 if no initial underscores found, 1 if there's only one underscore, 2 if there's two or more initial underscores.
*/
public static int getInitialUnderscores(String name) {
if (name == null) {
return 0;
}
int underscores = 0;
if (name.startsWith("__")) {
underscores = 2;
}
else if (name.startsWith("_")) underscores = 1;
return underscores;
}
/**
* Tries to find nearest parent that conceals names defined inside it. Such elements are 'class' and 'def':
* anything defined within it does not seep to the namespace below them, but is concealed within.
*
* @param elt starting point of search.
* @return 'class' or 'def' element, or null if not found.
* @deprecated Use {@link ScopeUtil#getScopeOwner} instead.
*/
@Deprecated
@Nullable
public static PsiElement getConcealingParent(PsiElement elt) {
if (elt == null || elt instanceof PsiFile) {
return null;
}
PsiElement parent = PsiTreeUtil.getStubOrPsiParent(elt);
boolean jump_over = false;
while (parent != null) {
if (parent instanceof PyClass || parent instanceof Callable) {
if (jump_over) {
jump_over = false;
}
else {
return parent;
}
}
else if (parent instanceof PyDecoratorList) {
// decorators PSI is inside decorated things but their namespace is outside
jump_over = true;
}
else if (parent instanceof PsiFileSystemItem) {
break;
}
parent = PsiTreeUtil.getStubOrPsiParent(parent);
}
return null;
}
/**
* @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) {
return name.startsWith("__") && !name.endsWith("__");
}
public static boolean isSpecialName(@NotNull String name) {
return name.length() > 4 && name.startsWith("__") && name.endsWith("__");
}
public static boolean isPythonIdentifier(@NotNull String name) {
return PyNames.isIdentifier(name);
}
public static LookupElement createNamedParameterLookup(String name) {
LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(name + "=").withIcon(PlatformIcons.PARAMETER_ICON);
lookupElementBuilder = lookupElementBuilder.withInsertHandler(OverwriteEqualsInsertHandler.INSTANCE);
return PrioritizedLookupElement.withGrouping(lookupElementBuilder, 1);
}
/**
* Peels argument expression of parentheses and of keyword argument wrapper
*
* @param expr an item of getArguments() array
* @return expression actually passed as argument
*/
@Nullable
public static PyExpression peelArgument(PyExpression expr) {
while (expr instanceof PyParenthesizedExpression) expr = ((PyParenthesizedExpression)expr).getContainedExpression();
if (expr instanceof PyKeywordArgument) expr = ((PyKeywordArgument)expr).getValueExpression();
return expr;
}
public static String getFirstParameterName(PyFunction container) {
String selfName = PyNames.CANONICAL_SELF;
if (container != null) {
final PyParameter[] params = container.getParameterList().getParameters();
if (params.length > 0) {
final PyNamedParameter named = params[0].getAsNamed();
if (named != null) {
selfName = named.getName();
}
}
}
return selfName;
}
/**
* @return Source roots <strong>and</strong> content roots for element's project
*/
@NotNull
public static Collection<VirtualFile> getSourceRoots(@NotNull PsiElement foothold) {
final Module module = ModuleUtilCore.findModuleForPsiElement(foothold);
if (module != null) {
return getSourceRoots(module);
}
return Collections.emptyList();
}
/**
* @return Source roots <strong>and</strong> content roots for module
*/
@NotNull
public static Collection<VirtualFile> getSourceRoots(@NotNull Module module) {
final Set<VirtualFile> result = new LinkedHashSet<VirtualFile>();
final ModuleRootManager manager = ModuleRootManager.getInstance(module);
result.addAll(Arrays.asList(manager.getSourceRoots()));
result.addAll(Arrays.asList(manager.getContentRoots()));
return result;
}
@Nullable
public static VirtualFile findInRoots(Module module, String path) {
if (module != null) {
for (VirtualFile root : getSourceRoots(module)) {
VirtualFile file = root.findFileByRelativePath(path);
if (file != null) {
return file;
}
}
}
return null;
}
@Nullable
public static List<String> getStringListFromTargetExpression(PyTargetExpression attr) {
return strListValue(attr.findAssignedValue());
}
@Nullable
public static List<String> strListValue(PyExpression value) {
while (value instanceof PyParenthesizedExpression) {
value = ((PyParenthesizedExpression)value).getContainedExpression();
}
if (value instanceof PySequenceExpression) {
final PyExpression[] elements = ((PySequenceExpression)value).getElements();
List<String> result = new ArrayList<String>(elements.length);
for (PyExpression element : elements) {
if (!(element instanceof PyStringLiteralExpression)) {
return null;
}
result.add(((PyStringLiteralExpression)element).getStringValue());
}
return result;
}
return null;
}
@NotNull
public static Map<String, PyExpression> dictValue(@NotNull PyDictLiteralExpression dict) {
Map<String, PyExpression> result = Maps.newLinkedHashMap();
for (PyKeyValueExpression keyValue : dict.getElements()) {
PyExpression key = keyValue.getKey();
PyExpression value = keyValue.getValue();
if (key instanceof PyStringLiteralExpression) {
result.put(((PyStringLiteralExpression)key).getStringValue(), value);
}
}
return result;
}
/**
* @param what thing to search for
* @param variants things to search among
* @return true iff what.equals() one of the variants.
*/
public static <T> boolean among(@NotNull T what, T... variants) {
for (T s : variants) {
if (what.equals(s)) return true;
}
return false;
}
@Nullable
public static String getKeywordArgumentString(PyCallExpression expr, String keyword) {
return PyPsiUtils.strValue(expr.getKeywordArgument(keyword));
}
public static boolean isExceptionClass(PyClass pyClass) {
if (isBaseException(pyClass.getQualifiedName())) {
return true;
}
for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) {
if (type != null && isBaseException(type.getClassQName())) {
return true;
}
}
return false;
}
private static boolean isBaseException(String name) {
return name != null && (name.contains("BaseException") || name.startsWith("exceptions."));
}
public static class MethodFlags {
private boolean myIsStaticMethod;
private boolean myIsMetaclassMethod;
private boolean myIsSpecialMetaclassMethod;
private boolean myIsClassMethod;
/**
* @return true iff the method belongs to a metaclass (an ancestor of 'type').
*/
public boolean isMetaclassMethod() {
return myIsMetaclassMethod;
}
/**
* @return iff isMetaclassMethod and the method is either __init__ or __call__.
*/
public boolean isSpecialMetaclassMethod() {
return myIsSpecialMetaclassMethod;
}
public boolean isStaticMethod() {
return myIsStaticMethod;
}
public boolean isClassMethod() {
return myIsClassMethod;
}
private MethodFlags(boolean isClassMethod, boolean isStaticMethod, boolean isMetaclassMethod, boolean isSpecialMetaclassMethod) {
myIsClassMethod = isClassMethod;
myIsStaticMethod = isStaticMethod;
myIsMetaclassMethod = isMetaclassMethod;
myIsSpecialMetaclassMethod = isSpecialMetaclassMethod;
}
/**
* @param node a function
* @return a new flags object, or null if the function is not a method
*/
@Nullable
public static MethodFlags of(@NotNull PyFunction node) {
PyClass cls = node.getContainingClass();
if (cls != null) {
PyFunction.Modifier modifier = node.getModifier();
boolean isMetaclassMethod = false;
PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type");
for (PyClass ancestor_cls : cls.getAncestorClasses()) {
if (ancestor_cls == type_cls) {
isMetaclassMethod = true;
break;
}
}
final String method_name = node.getName();
boolean isSpecialMetaclassMethod = isMetaclassMethod && method_name != null && among(method_name, PyNames.INIT, "__call__");
return new MethodFlags(modifier == CLASSMETHOD, modifier == STATICMETHOD, isMetaclassMethod, isSpecialMetaclassMethod);
}
return null;
}
//TODO: Doc
public boolean isInstanceMethod() {
return !(myIsClassMethod || myIsStaticMethod);
}
}
public static boolean isSuperCall(@NotNull PyCallExpression node) {
PyClass klass = PsiTreeUtil.getParentOfType(node, PyClass.class);
if (klass == null) return false;
PyExpression callee = node.getCallee();
if (callee == null) return false;
String name = callee.getName();
if (PyNames.SUPER.equals(name)) {
PsiReference reference = callee.getReference();
if (reference == null) return false;
PsiElement resolved = reference.resolve();
PyBuiltinCache cache = PyBuiltinCache.getInstance(node);
if (resolved != null && cache.isBuiltin(resolved)) {
PyExpression[] args = node.getArguments();
if (args.length > 0) {
String firstArg = args[0].getText();
if (firstArg.equals(klass.getName()) || firstArg.equals(PyNames.CANONICAL_SELF + "." + PyNames.__CLASS__)) {
return true;
}
for (PyClass s : klass.getAncestorClasses()) {
if (firstArg.equals(s.getName())) {
return true;
}
}
}
else {
return true;
}
}
}
return false;
}
@NotNull
public static PyFile getOrCreateFile(String path, Project project) {
final VirtualFile vfile = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
final PsiFile psi;
if (vfile == null) {
final File file = new File(path);
try {
final VirtualFile baseDir = project.getBaseDir();
final FileTemplateManager fileTemplateManager = FileTemplateManager.getInstance();
final FileTemplate template = fileTemplateManager.getInternalTemplate("Python Script");
final String content = (template != null) ? template.getText(fileTemplateManager.getDefaultProperties(project)) : null;
psi = PyExtractSuperclassHelper.placeFile(project,
StringUtil.notNullize(
file.getParent(),
baseDir != null ? baseDir
.getPath() : "."
),
file.getName(),
content
);
}
catch (IOException e) {
throw new IncorrectOperationException(String.format("Cannot create file '%s'", path));
}
}
else {
psi = PsiManager.getInstance(project).findFile(vfile);
}
if (!(psi instanceof PyFile)) {
throw new IncorrectOperationException(PyBundle.message(
"refactoring.move.class.or.function.error.cannot.place.elements.into.nonpython.file"));
}
return (PyFile)psi;
}
/**
* counts elements in iterable
*
* @param expression to count containing elements (iterable)
* @return element count
*/
public static int getElementsCount(PyExpression expression, TypeEvalContext evalContext) {
int valuesLength = -1;
PyType type = evalContext.getType(expression);
if (type instanceof PyTupleType) {
valuesLength = ((PyTupleType)type).getElementCount();
}
else if (type instanceof PyNamedTupleType) {
valuesLength = ((PyNamedTupleType)type).getElementCount();
}
else if (expression instanceof PySequenceExpression) {
valuesLength = ((PySequenceExpression)expression).getElements().length;
}
else if (expression instanceof PyStringLiteralExpression) {
valuesLength = ((PyStringLiteralExpression)expression).getStringValue().length();
}
else if (expression instanceof PyNumericLiteralExpression) {
valuesLength = 1;
}
else if (expression instanceof PyCallExpression) {
PyCallExpression call = (PyCallExpression)expression;
if (call.isCalleeText("dict")) {
valuesLength = call.getArguments().length;
}
else if (call.isCalleeText("tuple")) {
PyExpression[] arguments = call.getArguments();
if (arguments.length > 0 && arguments[0] instanceof PySequenceExpression) {
valuesLength = ((PySequenceExpression)arguments[0]).getElements().length;
}
}
}
return valuesLength;
}
@Nullable
public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) {
PsiElement element;
if (caretOffset < 0) {
return null;
}
int lineStartOffset = 0;
final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
if (document != null) {
int lineNumber = document.getLineNumber(caretOffset);
lineStartOffset = document.getLineStartOffset(lineNumber);
}
do {
caretOffset--;
element = psiFile.findElementAt(caretOffset);
}
while (caretOffset >= lineStartOffset && instanceOf(element, toSkip));
return instanceOf(element, toSkip) ? null : element;
}
@Nullable
public static PsiElement findNonWhitespaceAtOffset(PsiFile psiFile, int caretOffset) {
PsiElement element = findNextAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
if (element == null) {
element = findPrevAtOffset(psiFile, caretOffset - 1, PsiWhiteSpace.class);
}
return element;
}
@Nullable
public static PsiElement findElementAtOffset(PsiFile psiFile, int caretOffset) {
PsiElement element = findPrevAtOffset(psiFile, caretOffset);
if (element == null) {
element = findNextAtOffset(psiFile, caretOffset);
}
return element;
}
@Nullable
public static PsiElement findNextAtOffset(@NotNull final PsiFile psiFile, int caretOffset, Class... toSkip) {
PsiElement element = psiFile.findElementAt(caretOffset);
if (element == null) {
return null;
}
final Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
int lineEndOffset = 0;
if (document != null) {
int lineNumber = document.getLineNumber(caretOffset);
lineEndOffset = document.getLineEndOffset(lineNumber);
}
while (caretOffset < lineEndOffset && instanceOf(element, toSkip)) {
caretOffset++;
element = psiFile.findElementAt(caretOffset);
}
return instanceOf(element, toSkip) ? null : element;
}
/**
* Adds element to statement list to the correct place according to its dependencies.
*
* @param element to insert
* @param statementList where element should be inserted
* @return inserted element
*/
public static <T extends PyElement> T addElementToStatementList(@NotNull final T element,
@NotNull final PyStatementList statementList) {
PsiElement before = null;
PsiElement after = null;
for (final PyStatement statement : statementList.getStatements()) {
if (PyDependenciesComparator.depends(element, statement)) {
after = statement;
}
else if (PyDependenciesComparator.depends(statement, element)) {
before = statement;
}
}
final PsiElement result;
if (after != null) {
result = statementList.addAfter(element, after);
}
else if (before != null) {
result = statementList.addBefore(element, before);
}
else {
result = addElementToStatementList(element, statementList, true);
}
@SuppressWarnings("unchecked") // Inserted element can't have different type
final T resultCasted = (T)result;
return resultCasted;
}
public static PsiElement addElementToStatementList(@NotNull PsiElement element,
@NotNull PyStatementList statementList,
boolean toTheBeginning) {
final PsiElement firstChild = statementList.getFirstChild();
if (firstChild == statementList.getLastChild() && firstChild instanceof PyPassStatement) {
element = firstChild.replace(element);
}
else {
final PyStatement[] statements = statementList.getStatements();
if (toTheBeginning && statements.length > 0) {
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(statementList, PyDocStringOwner.class);
PyStatement anchor = statements[0];
if (docStringOwner != null && anchor instanceof PyExpressionStatement &&
((PyExpressionStatement)anchor).getExpression() == docStringOwner.getDocStringExpression()) {
final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
if (next == null) {
return statementList.addAfter(element, anchor);
}
anchor = next;
}
while (anchor instanceof PyExpressionStatement) {
final PyExpression expression = ((PyExpressionStatement)anchor).getExpression();
if (expression instanceof PyCallExpression) {
final PyExpression callee = ((PyCallExpression)expression).getCallee();
if ((isSuperCall((PyCallExpression)expression) || (callee != null && PyNames.INIT.equals(callee.getName())))) {
final PyStatement next = PsiTreeUtil.getNextSiblingOfType(anchor, PyStatement.class);
if (next == null) {
return statementList.addAfter(element, anchor);
}
anchor = next;
}
else {
break;
}
}
else {
break;
}
}
element = statementList.addBefore(element, anchor);
}
else {
element = statementList.add(element);
}
}
return element;
}
@NotNull
public static List<PyParameter> getParameters(@NotNull Callable callable, @NotNull TypeEvalContext context) {
PyType type = context.getType(callable);
if (type instanceof PyUnionType) {
type = ((PyUnionType)type).excludeNull(context);
}
if (type instanceof PyCallableType) {
final PyCallableType callableType = (PyCallableType)type;
final List<PyCallableParameter> callableTypeParameters = callableType.getParameters(context);
if (callableTypeParameters != null) {
boolean allParametersDefined = true;
final List<PyParameter> parameters = new ArrayList<PyParameter>();
for (PyCallableParameter callableParameter : callableTypeParameters) {
final PyParameter parameter = callableParameter.getParameter();
if (parameter == null) {
allParametersDefined = false;
break;
}
parameters.add(parameter);
}
if (allParametersDefined) {
return parameters;
}
}
}
return Arrays.asList(callable.getParameterList().getParameters());
}
public static boolean isSignatureCompatibleTo(@NotNull Callable callable, @NotNull Callable otherCallable,
@NotNull TypeEvalContext context) {
final List<PyParameter> parameters = getParameters(callable, context);
final List<PyParameter> otherParameters = getParameters(otherCallable, context);
final int optionalCount = optionalParametersCount(parameters);
final int otherOptionalCount = optionalParametersCount(otherParameters);
final int requiredCount = requiredParametersCount(callable, parameters);
final int otherRequiredCount = requiredParametersCount(otherCallable, otherParameters);
if (hasPositionalContainer(otherParameters) || hasKeywordContainer(otherParameters)) {
if (otherParameters.size() == specialParametersCount(otherCallable, otherParameters)) {
return true;
}
}
if (hasPositionalContainer(parameters) || hasKeywordContainer(parameters)) {
return requiredCount <= otherRequiredCount;
}
return requiredCount <= otherRequiredCount && parameters.size() >= otherParameters.size() && optionalCount >= otherOptionalCount;
}
private static int optionalParametersCount(@NotNull List<PyParameter> parameters) {
int n = 0;
for (PyParameter parameter : parameters) {
if (parameter.hasDefaultValue()) {
n++;
}
}
return n;
}
private static int requiredParametersCount(@NotNull Callable callable, @NotNull List<PyParameter> parameters) {
return parameters.size() - optionalParametersCount(parameters) - specialParametersCount(callable, parameters);
}
private static int specialParametersCount(@NotNull Callable callable, @NotNull List<PyParameter> parameters) {
int n = 0;
if (hasPositionalContainer(parameters)) {
n++;
}
if (hasKeywordContainer(parameters)) {
n++;
}
if (callable.asMethod() != null) {
n++;
}
else {
if (parameters.size() > 0) {
final PyParameter first = parameters.get(0);
if (PyNames.CANONICAL_SELF.equals(first.getName())) {
n++;
}
}
}
return n;
}
private static boolean hasPositionalContainer(@NotNull List<PyParameter> parameters) {
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isPositionalContainer()) {
return true;
}
}
return false;
}
private static boolean hasKeywordContainer(@NotNull List<PyParameter> parameters) {
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isKeywordContainer()) {
return true;
}
}
return false;
}
public static boolean isInit(@NotNull final PyFunction function) {
return PyNames.INIT.equals(function.getName());
}
/**
* Filters out {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}
* that should not be displayed in this refactoring (like object)
*
* @param pyMemberInfos collection to sort
* @return sorted collection
*/
@NotNull
public static Collection<PyMemberInfo<PyElement>> filterOutObject(@NotNull final Collection<PyMemberInfo<PyElement>> pyMemberInfos) {
return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
}
public static boolean isStarImportableFrom(@NotNull String name, @NotNull PyFile file) {
final List<String> dunderAll = file.getDunderAll();
return dunderAll != null ? dunderAll.contains(name) : !name.startsWith("_");
}
/**
* Filters only pyclass object (new class)
*/
public static class ObjectPredicate extends NotNullPredicate<PyMemberInfo<PyElement>> {
private final boolean myAllowObjects;
/**
* @param allowObjects allows only objects if true. Allows all but objects otherwise.
*/
public ObjectPredicate(final boolean allowObjects) {
myAllowObjects = allowObjects;
}
@Override
public boolean applyNotNull(@NotNull final PyMemberInfo<PyElement> input) {
return myAllowObjects == isObject(input);
}
private static boolean isObject(@NotNull final PyMemberInfo<PyElement> classMemberInfo) {
final PyElement element = classMemberInfo.getMember();
if ((element instanceof PyClass) && PyNames.OBJECT.equals(element.getName())) {
return true;
}
return false;
}
}
/**
* Sometimes you do not know real FQN of some class, but you know class name and its package.
* I.e. <code>django.apps.conf.AppConfig</code> is not documented, but you know
* <code>AppConfig</code> and <code>django</code> package.
*
* @param symbol element to check (class or function)
* @param expectedPackage package like "django"
* @param expectedName expected name (i.e. AppConfig)
* @return true if element in package
*/
public static boolean isSymbolInPackage(@NotNull final PyQualifiedNameOwner symbol,
@NotNull final String expectedPackage,
@NotNull final String expectedName) {
final String qualifiedNameString = symbol.getQualifiedName();
if (qualifiedNameString == null) {
return false;
}
final QualifiedName qualifiedName = QualifiedName.fromDottedString(qualifiedNameString);
final String aPackage = qualifiedName.getFirstComponent();
if (!(expectedPackage.equals(aPackage))) {
return false;
}
final String symboldName = qualifiedName.getLastComponent();
return expectedName.equals(symboldName);
}
/**
* Checks that given class is the root of class hierarchy, i.e. it's either {@code object} or
* special {@link com.jetbrains.python.PyNames#FAKE_OLD_BASE} class for old-style classes.
*
* @param cls Python class to check
* @see com.jetbrains.python.psi.impl.PyBuiltinCache
* @see PyNames#FAKE_OLD_BASE
*/
public static boolean isObjectClass(@NotNull PyClass cls) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(cls);
if (cls == builtinCache.getClass(PyNames.OBJECT) || cls == builtinCache.getClass(PyNames.FAKE_OLD_BASE)) {
return true;
}
return false;
}
/**
* Checks that given type is the root of type hierarchy, i.e. it's type of either {@code object} or special
* {@link com.jetbrains.python.PyNames#FAKE_OLD_BASE} class for old-style classes.
*
* @param type Python class to check
* @param anchor arbitrary PSI element to find appropriate SDK
* @see com.jetbrains.python.psi.impl.PyBuiltinCache
* @see PyNames#FAKE_OLD_BASE
*/
public static boolean isObjectType(@NotNull PyType type, @NotNull PsiElement anchor) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
if (type == builtinCache.getObjectType() || type == builtinCache.getOldstyleClassobjType()) {
return true;
}
return false;
}
}