blob: 8049546a6e921f8c8ce7bb007da67fd85dd0bc2c [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.inspections.unresolvedReference;
import com.google.common.collect.ImmutableSet;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.ListEditForm;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.*;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.Consumer;
import com.intellij.util.PlatformUtils;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.codeInsight.PyDynamicMember;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.imports.AutoImportHintAction;
import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix;
import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix;
import com.jetbrains.python.codeInsight.imports.PythonReferenceImporter;
import com.jetbrains.python.console.PydevConsoleRunner;
import com.jetbrains.python.documentation.DocStringParameterReference;
import com.jetbrains.python.documentation.DocStringTypeReference;
import com.jetbrains.python.inspections.*;
import com.jetbrains.python.inspections.quickfix.*;
import com.jetbrains.python.packaging.PyPIPackageUtil;
import com.jetbrains.python.packaging.PyRequirement;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyImportStatementNavigator;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.references.PyImportReference;
import com.jetbrains.python.psi.impl.references.PyOperatorReference;
import com.jetbrains.python.psi.resolve.ImportedResolveResult;
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.types.*;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import static com.jetbrains.python.inspections.quickfix.AddIgnoredIdentifierQuickFix.END_WILDCARD;
/**
* Marks references that fail to resolve. Also tracks unused imports and provides "optimize imports" support.
* User: dcheryasov
* Date: Nov 15, 2008
*/
public class PyUnresolvedReferencesInspection extends PyInspection {
private static Key<Visitor> KEY = Key.create("PyUnresolvedReferencesInspection.Visitor");
public static final Key<PyUnresolvedReferencesInspection> SHORT_NAME_KEY =
Key.create(PyUnresolvedReferencesInspection.class.getSimpleName());
public JDOMExternalizableStringList ignoredIdentifiers = new JDOMExternalizableStringList();
public static PyUnresolvedReferencesInspection getInstance(PsiElement element) {
final InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(element.getProject()).getInspectionProfile();
return (PyUnresolvedReferencesInspection)inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element);
}
@Nls
@NotNull
public String getDisplayName() {
return PyBundle.message("INSP.NAME.unresolved.refs");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
final boolean isOnTheFly,
@NotNull final LocalInspectionToolSession session) {
final Visitor visitor = new Visitor(holder, session, ignoredIdentifiers);
// buildVisitor() will be called on injected files in the same session - don't overwrite if we already have one
final Visitor existingVisitor = session.getUserData(KEY);
if (existingVisitor == null) {
session.putUserData(KEY, visitor);
}
return visitor;
}
@Override
public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder holder) {
final Visitor visitor = session.getUserData(KEY);
assert visitor != null;
if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) {
visitor.highlightUnusedImports();
}
session.putUserData(KEY, null);
}
@Override
public JComponent createOptionsPanel() {
ListEditForm form = new ListEditForm("Ignore references", ignoredIdentifiers);
return form.getContentPanel();
}
public static class Visitor extends PyInspectionVisitor {
private Set<PsiElement> myUsedImports = Collections.synchronizedSet(new HashSet<PsiElement>());
private Set<PyImportedNameDefiner> myAllImports = Collections.synchronizedSet(new HashSet<PyImportedNameDefiner>());
private final ImmutableSet<String> myIgnoredIdentifiers;
private volatile Boolean myIsEnabled = null;
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session, List<String> ignoredIdentifiers) {
super(holder, session);
myIgnoredIdentifiers = ImmutableSet.copyOf(ignoredIdentifiers);
}
public boolean isEnabled(@NotNull PsiElement anchor) {
if (myIsEnabled == null) {
final boolean isPyCharm = PlatformUtils.isPyCharm();
myIsEnabled = (isPyCharm && PythonSdkType.getSdk(anchor) != null || !isPyCharm) &&
!PySkeletonRefresher.isGeneratingSkeletons();
}
return myIsEnabled;
}
@Override
public void visitPyTargetExpression(PyTargetExpression node) {
checkSlots(node);
}
private void checkSlots(PyQualifiedExpression node) {
final PyExpression qualifier = node.getQualifier();
if (qualifier != null) {
final PyType type = myTypeEvalContext.getType(qualifier);
if (type instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)type).getPyClass();
if (pyClass.isNewStyleClass()) {
if (pyClass.getOwnSlots() == null) {
return;
}
final List<String> slots = pyClass.getSlots();
final String attrName = node.getReferencedName();
if (slots != null && !slots.contains(attrName) && !slots.contains(PyNames.DICT)) {
for (PyClass ancestor : pyClass.getAncestorClasses(myTypeEvalContext)) {
if (ancestor == null) {
return;
}
if (PyNames.OBJECT.equals(ancestor.getName())) {
break;
}
final List<String> ancestorSlots = ancestor.getSlots();
if (ancestorSlots == null || ancestorSlots.contains(attrName) || ancestorSlots.contains(PyNames.DICT)) {
return;
}
}
final ASTNode nameNode = node.getNameElement();
final PsiElement e = nameNode != null ? nameNode.getPsi() : node;
registerProblem(e, "'" + pyClass.getName() + "' object has no attribute '" + attrName + "'");
}
}
}
}
}
@Override
public void visitPyImportElement(PyImportElement node) {
super.visitPyImportElement(node);
final PyFromImportStatement fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class);
if (isEnabled(node) && (fromImport == null || !fromImport.isFromFuture())) {
myAllImports.add(node);
}
}
@Override
public void visitPyStarImportElement(PyStarImportElement node) {
super.visitPyStarImportElement(node);
if (isEnabled(node)) {
myAllImports.add(node);
}
}
@Nullable
private static PyExceptPart getImportErrorGuard(PyElement node) {
final PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class);
if (importStatement != null) {
final PyTryPart tryPart = PsiTreeUtil.getParentOfType(node, PyTryPart.class);
if (tryPart != null) {
final PyTryExceptStatement tryExceptStatement = PsiTreeUtil.getParentOfType(tryPart, PyTryExceptStatement.class);
if (tryExceptStatement != null) {
for (PyExceptPart exceptPart : tryExceptStatement.getExceptParts()) {
final PyExpression expr = exceptPart.getExceptClass();
if (expr != null && "ImportError".equals(expr.getName())) {
return exceptPart;
}
}
}
}
}
return null;
}
private static boolean isGuardedByHasattr(@NotNull final PyElement node, @NotNull final String name) {
final String nodeName = node.getName();
if (nodeName != null) {
final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, nodeName);
PyElement e = PsiTreeUtil.getParentOfType(node, PyConditionalStatementPart.class, PyConditionalExpression.class);
while (e != null && PsiTreeUtil.isAncestor(owner, e, true)) {
final ArrayList<PyCallExpression> calls = new ArrayList<PyCallExpression>();
PyExpression cond = null;
if (e instanceof PyConditionalStatementPart) {
cond = ((PyConditionalStatementPart)e).getCondition();
}
else if (e instanceof PyConditionalExpression && PsiTreeUtil.isAncestor(((PyConditionalExpression)e).getTruePart(), node, true)) {
cond = ((PyConditionalExpression)e).getCondition();
}
if (cond instanceof PyCallExpression) {
calls.add((PyCallExpression)cond);
}
if (cond != null) {
final PyCallExpression[] callExpressions = PsiTreeUtil.getChildrenOfType(cond, PyCallExpression.class);
if (callExpressions != null) {
calls.addAll(Arrays.asList(callExpressions));
}
for (PyCallExpression call : calls) {
final PyExpression callee = call.getCallee();
final PyExpression[] args = call.getArguments();
// TODO: Search for `node` aliases using aliases analysis
if (callee != null && "hasattr".equals(callee.getName()) && args.length == 2 &&
nodeName.equals(args[0].getName()) && args[1] instanceof PyStringLiteralExpression &&
((PyStringLiteralExpression)args[1]).getStringValue().equals(name)) {
return true;
}
}
}
e = PsiTreeUtil.getParentOfType(e, PyConditionalStatementPart.class);
}
}
return false;
}
@Override
public void visitPyElement(final PyElement node) {
super.visitPyElement(node);
final PsiFile file = node.getContainingFile();
final InjectedLanguageManager injectedLanguageManager =
InjectedLanguageManager.getInstance(node.getProject());
if (injectedLanguageManager.isInjectedFragment(file)) {
final PsiLanguageInjectionHost host =
injectedLanguageManager.getInjectionHost(node);
processInjection(host);
}
if (node instanceof PyReferenceOwner) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
processReference(node, ((PyReferenceOwner)node).getReference(resolveContext));
}
else {
if (node instanceof PsiLanguageInjectionHost) {
processInjection((PsiLanguageInjectionHost)node);
}
for (final PsiReference reference : node.getReferences()) {
processReference(node, reference);
}
}
}
private void processInjection(@Nullable PsiLanguageInjectionHost node) {
if (node == null) return;
final List<Pair<PsiElement, TextRange>>
files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node);
if (files != null) {
for (Pair<PsiElement, TextRange> pair : files) {
new PyRecursiveElementVisitor() {
@Override
public void visitPyElement(PyElement element) {
super.visitPyElement(element);
if (element instanceof PyReferenceOwner) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
final PsiPolyVariantReference reference = ((PyReferenceOwner)element).getReference(resolveContext);
if (reference != null) {
final ResolveResult[] resolveResults = reference.multiResolve(false);
for (ResolveResult resolveResult : resolveResults) {
if (resolveResult instanceof ImportedResolveResult) {
myUsedImports.addAll(((ImportedResolveResult)resolveResult).getNameDefiners());
}
}
}
}
}
}.visitElement(pair.getFirst());
}
}
}
private void processReference(PyElement node, @Nullable PsiReference reference) {
if (!isEnabled(node) || reference == null || reference.isSoft()) {
return;
}
HighlightSeverity severity = HighlightSeverity.ERROR;
if (reference instanceof PsiReferenceEx) {
severity = ((PsiReferenceEx)reference).getUnresolvedHighlightSeverity(myTypeEvalContext);
if (severity == null) return;
}
PyExceptPart guard = getImportErrorGuard(node);
if (guard != null) {
processReferenceInImportGuard(node, guard);
return;
}
if (node instanceof PyQualifiedExpression) {
final PyQualifiedExpression qExpr = (PyQualifiedExpression)node;
final PyExpression qualifier = qExpr.getQualifier();
final String name = node.getName();
if (qualifier != null && name != null && isGuardedByHasattr(qualifier, name)) {
return;
}
}
PsiElement target = null;
boolean unresolved;
if (reference instanceof PsiPolyVariantReference) {
final PsiPolyVariantReference poly = (PsiPolyVariantReference)reference;
final ResolveResult[] resolveResults = poly.multiResolve(false);
unresolved = (resolveResults.length == 0);
for (ResolveResult resolveResult : resolveResults) {
if (target == null && resolveResult.isValidResult()) {
target = resolveResult.getElement();
}
if (resolveResult instanceof ImportedResolveResult) {
myUsedImports.addAll(((ImportedResolveResult)resolveResult).getNameDefiners());
}
}
}
else {
target = reference.resolve();
unresolved = (target == null);
}
if (unresolved) {
boolean ignoreUnresolved = false;
for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) {
if (extension.ignoreUnresolvedReference(node, reference)) {
ignoreUnresolved = true;
break;
}
}
if (!ignoreUnresolved) {
registerUnresolvedReferenceProblem(node, reference, severity);
}
// don't highlight unresolved imports as unused
if (node.getParent() instanceof PyImportElement) {
myAllImports.remove(node.getParent());
}
}
else if (reference instanceof PyImportReference &&
target == reference.getElement().getContainingFile() &&
!isContainingFileImportAllowed(node, (PsiFile)target)) {
registerProblem(node, "Import resolves to its containing file");
}
}
private static boolean isContainingFileImportAllowed(PyElement node, PsiFile target) {
// import resolving to containing file is allowed when we're importing from the current package and the containing file
// is __init__.py (PY-5265)
final boolean insideFromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class) != null;
if (!insideFromImport) {
return false;
}
if (PyImportStatementNavigator.getImportStatementByElement(node) != null) {
return false;
}
return target.getName().equals(PyNames.INIT_DOT_PY);
}
private void processReferenceInImportGuard(PyElement node, PyExceptPart guard) {
final PyImportElement importElement = PsiTreeUtil.getParentOfType(node, PyImportElement.class);
if (importElement != null) {
final String visibleName = importElement.getVisibleName();
final ScopeOwner owner = ScopeUtil.getScopeOwner(importElement);
if (visibleName != null && owner != null) {
final Collection<PsiElement> allWrites = ScopeUtil.getReadWriteElements(visibleName, owner, false, true);
final Collection<PsiElement> writesInsideGuard = new ArrayList<PsiElement>();
for (PsiElement write : allWrites) {
if (PsiTreeUtil.isAncestor(guard, write, false)) {
writesInsideGuard.add(write);
}
}
if (writesInsideGuard.isEmpty()) {
final PyTargetExpression asElement = importElement.getAsNameElement();
final PyElement toHighlight = asElement != null ? asElement : node;
registerProblem(toHighlight,
PyBundle.message("INSP.try.except.import.error",
visibleName),
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
}
}
private void registerUnresolvedReferenceProblem(@NotNull PyElement node, @NotNull final PsiReference reference,
@NotNull HighlightSeverity severity) {
if (reference instanceof DocStringTypeReference) {
return;
}
String description = null;
PsiElement element = reference.getElement();
final String text = element.getText();
TextRange rangeInElement = reference.getRangeInElement();
String refText = text; // text of the part we're working with
if (rangeInElement.getStartOffset() > 0 && rangeInElement.getEndOffset() > 0) {
refText = rangeInElement.substring(text);
}
final List<LocalQuickFix> actions = new ArrayList<LocalQuickFix>(2);
final String refName = (element instanceof PyQualifiedExpression) ? ((PyQualifiedExpression)element).getReferencedName() : refText;
// Empty text, nothing to highlight
if (refName == null || refName.length() <= 0) {
return;
}
final QualifiedName canonicalQName = getCanonicalName(reference, myTypeEvalContext);
final String canonicalName = canonicalQName != null ? canonicalQName.toString() : null;
if (canonicalName != null) {
for (String ignored : myIgnoredIdentifiers) {
if (ignored.endsWith(END_WILDCARD)) {
final String prefix = ignored.substring(0, ignored.length() - END_WILDCARD.length());
if (canonicalName.startsWith(prefix)) {
return;
}
}
else if (canonicalName.equals(ignored)) {
return;
}
}
}
// Legacy non-qualified ignore patterns
if (myIgnoredIdentifiers.contains(refName)) {
return;
}
if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
if (PyNames.COMPARISON_OPERATORS.contains(refName)) {
return;
}
if (expr.isQualified()) {
final PyClassTypeImpl object_type = (PyClassTypeImpl)PyBuiltinCache.getInstance(node).getObjectType();
if ((object_type != null) && object_type.getPossibleInstanceMembers().contains(refName)){
return;
}
}
else {
if (PyUnreachableCodeInspection.hasAnyInterruptedControlFlowPaths(expr)) {
return;
}
if (LanguageLevel.forElement(node).isOlderThan(LanguageLevel.PYTHON26)) {
if ("with".equals(refName)) {
actions.add(new UnresolvedRefAddFutureImportQuickFix());
}
}
if (refText.equals("true") || refText.equals("false")) {
actions.add(new UnresolvedRefTrueFalseQuickFix(element));
}
addAddSelfFix(node, expr, actions);
PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
if (callExpression != null && (!(callExpression.getCallee() instanceof PyQualifiedExpression) ||
((PyQualifiedExpression)callExpression.getCallee()).getQualifier() == null)) {
actions.add(new UnresolvedRefCreateFunctionQuickFix(callExpression, expr));
}
PyFunction parentFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class);
PyDecorator decorator = PsiTreeUtil.getParentOfType(element, PyDecorator.class);
PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class);
if (parentFunction != null && decorator == null && importStatement == null) {
actions.add(new UnresolvedReferenceAddParameterQuickFix(refName));
}
actions.add(new PyRenameUnresolvedRefQuickFix());
}
// unqualified:
// may be module's
if (PyModuleType.getPossibleInstanceMembers().contains(refName)) {
return;
}
// may be a "try: import ..."; not an error not to resolve
if ((
PsiTreeUtil.getParentOfType(
PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class
) != null
)) {
severity = HighlightSeverity.WEAK_WARNING;
description = PyBundle.message("INSP.module.$0.not.found", refText);
// TODO: mark the node so that future references pointing to it won't result in a error, but in a warning
}
}
if (reference instanceof PsiReferenceEx && description == null) {
description = ((PsiReferenceEx)reference).getUnresolvedDescription();
}
if (description == null) {
boolean markedQualified = false;
if (element instanceof PyQualifiedExpression) {
// TODO: Add __qualname__ for Python 3.3 to the skeleton of <class 'object'>, introduce a pseudo-class skeleton for
// <class 'function'>
if ("__qualname__".equals(refText) && LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON33)) {
return;
}
final PyQualifiedExpression expr = (PyQualifiedExpression)element;
if (PyNames.COMPARISON_OPERATORS.contains(expr.getReferencedName())) {
return;
}
final PyExpression qualifier = expr.getQualifier();
if (qualifier != null) {
PyType type = myTypeEvalContext.getType(qualifier);
if (type != null) {
if (ignoreUnresolvedMemberForType(type, reference, refName)) {
return;
}
addCreateMemberFromUsageFixes(type, reference, refText, actions);
if (type instanceof PyClassTypeImpl) {
if (reference instanceof PyOperatorReference) {
description = PyBundle.message("INSP.unresolved.operator.ref",
type.getName(), refName,
((PyOperatorReference)reference).getReadableOperatorName());
}
else {
description = PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", refText, type.getName());
}
markedQualified = true;
}
else {
description = PyBundle.message("INSP.cannot.find.$0.in.$1", refText, type.getName());
markedQualified = true;
}
}
}
}
if (!markedQualified) {
description = PyBundle.message("INSP.unresolved.ref.$0", refText);
// look in other imported modules for this whole name
if (PythonReferenceImporter.isImportable(element)) {
addAutoImportFix(node, reference, actions);
}
addCreateClassFix(refText, element, actions);
}
}
ProblemHighlightType hl_type;
if (severity == HighlightSeverity.WARNING) {
hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
}
else if (severity == HighlightSeverity.ERROR) {
hl_type = ProblemHighlightType.GENERIC_ERROR;
}
else {
hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL;
}
if (element != null) {
PyImportStatementBase importStatementBase = PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class);
if ((importStatementBase != null) && GenerateBinaryStubsFix.isApplicable(importStatementBase)) {
actions.addAll(GenerateBinaryStubsFix.generateFixes(importStatementBase));
}
}
if (canonicalQName != null) {
actions.add(new AddIgnoredIdentifierQuickFix(canonicalQName, false));
if (canonicalQName.getComponentCount() > 1) {
actions.add(new AddIgnoredIdentifierQuickFix(canonicalQName.removeLastComponent(), true));
}
}
addPluginQuickFixes(reference, actions);
if (reference instanceof PyImportReference) {
// TODO: Ignore references in the second part of the 'from ... import ...' expression
final QualifiedName qname = QualifiedName.fromDottedString(refName);
final List<String> components = qname.getComponents();
if (!components.isEmpty()) {
final String packageName = components.get(0);
final Module module = ModuleUtilCore.findModuleForPsiElement(node);
final Sdk sdk = PythonSdkType.findPythonSdk(module);
if (module != null && sdk != null) {
if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) {
final List<PyRequirement> requirements = Collections.singletonList(new PyRequirement(packageName));
final String name = "Install package " + packageName;
actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements));
}
}
}
}
registerProblem(node, description, hl_type, null, rangeInElement, actions.toArray(new LocalQuickFix[actions.size()]));
}
/**
* Return the canonical qualified name for a reference (even for an unresolved one).
*/
@Nullable
private static QualifiedName getCanonicalName(@NotNull PsiReference reference, @NotNull TypeEvalContext context) {
final PsiElement element = reference.getElement();
if (reference instanceof PyOperatorReference && element instanceof PyQualifiedExpression) {
final PyExpression receiver = ((PyOperatorReference)reference).getReceiver();
if (receiver != null) {
final PyType type = context.getType(receiver);
if (type instanceof PyClassType) {
final String name = ((PyClassType)type).getClassQName();
if (name != null) {
return QualifiedName.fromDottedString(name).append(((PyQualifiedExpression)element).getReferencedName());
}
}
}
}
else if (element instanceof PyReferenceExpression) {
final PyReferenceExpression expr = (PyReferenceExpression)element;
final PyExpression qualifier = expr.getQualifier();
final String exprName = expr.getName();
if (exprName != null) {
if (qualifier != null) {
final PyType type = context.getType(qualifier);
if (type instanceof PyClassType) {
final String name = ((PyClassType)type).getClassQName();
if (name != null) {
return QualifiedName.fromDottedString(name).append(exprName);
}
}
else if (type instanceof PyModuleType) {
final PyFile file = ((PyModuleType)type).getModule();
final QualifiedName name = QualifiedNameFinder.findCanonicalImportPath(file, element);
if (name != null) {
return name.append(exprName);
}
}
}
else {
final PsiElement parent = element.getParent();
if (parent instanceof PyImportElement) {
final PyImportStatementBase importStmt = PsiTreeUtil.getParentOfType(parent, PyImportStatementBase.class);
if (importStmt instanceof PyImportStatement) {
return QualifiedName.fromComponents(exprName);
}
else if (importStmt instanceof PyFromImportStatement) {
final PsiElement resolved = ((PyFromImportStatement)importStmt).resolveImportSource();
if (resolved != null) {
final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(resolved, element);
if (path != null) {
return path.append(exprName);
}
}
}
}
else {
final QualifiedName path = QualifiedNameFinder.findCanonicalImportPath(element, element);
if (path != null) {
return path.append(exprName);
}
}
}
}
}
else if (reference instanceof DocStringParameterReference) {
return QualifiedName.fromDottedString(reference.getCanonicalText());
}
return null;
}
private boolean ignoreUnresolvedMemberForType(@NotNull PyType type, PsiReference reference, String name) {
if (type instanceof PyNoneType || PyTypeChecker.isUnknown(type)) {
// this almost always means that we don't know the type, so don't show an error in this case
return true;
}
if (type instanceof PyImportedModuleType) {
PyImportedModule module = ((PyImportedModuleType)type).getImportedModule();
if (module.resolve() == null) {
return true;
}
}
if (type instanceof PyClassTypeImpl) {
PyClass cls = ((PyClassType)type).getPyClass();
if (overridesGetAttr(cls, myTypeEvalContext)) {
return true;
}
if (cls.findProperty(name, true) != null) {
return true;
}
if (PyUtil.hasUnresolvedAncestors(cls, myTypeEvalContext)) {
return true;
}
if (isDecoratedAsDynamic(cls, true)) {
return true;
}
if (hasUnresolvedDynamicMember((PyClassType)type, reference, name)) return true;
}
if (type instanceof PyFunctionType) {
final Callable callable = ((PyFunctionType)type).getCallable();
if (callable instanceof PyFunction && ((PyFunction)callable).getDecoratorList() != null) {
return true;
}
}
for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) {
if (extension.ignoreUnresolvedMember(type, name)) {
return true;
}
}
return false;
}
private static boolean hasUnresolvedDynamicMember(@NotNull final PyClassType type,
PsiReference reference,
@NotNull final String name) {
for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
final Collection<PyDynamicMember> resolveResult = provider.getMembers(type, reference.getElement());
for (PyDynamicMember member : resolveResult) {
if (member.getName().equals(name)) return true;
}
}
return false;
}
private boolean isDecoratedAsDynamic(@NotNull PyClass cls, boolean inherited) {
if (inherited) {
if (isDecoratedAsDynamic(cls, false)) {
return true;
}
for (PyClass base : cls.getAncestorClasses(myTypeEvalContext)) {
if (base != null && isDecoratedAsDynamic(base, false)) {
return true;
}
}
}
else {
if (cls.getDecoratorList() != null) {
return true;
}
final String docString = cls.getDocStringValue();
if (docString != null && docString.contains("@DynamicAttrs")) {
return true;
}
}
return false;
}
private void addCreateMemberFromUsageFixes(PyType type, PsiReference reference, String refText, List<LocalQuickFix> actions) {
PsiElement element = reference.getElement();
if (type instanceof PyClassTypeImpl) {
PyClass cls = ((PyClassType)type).getPyClass();
if (!PyBuiltinCache.getInstance(element).isBuiltin(cls)) {
if (element.getParent() instanceof PyCallExpression) {
actions.add(new AddMethodQuickFix(refText, cls.getName(), true));
}
else if (!(reference instanceof PyOperatorReference)) {
actions.add(new AddFieldQuickFix(refText, "None", type.getName(), true));
}
}
}
else if (type instanceof PyModuleType) {
PyFile file = ((PyModuleType)type).getModule();
actions.add(new AddFunctionQuickFix(refText, file.getName()));
addCreateClassFix(refText, element, actions);
}
}
private void addAddSelfFix(PyElement node, PyReferenceExpression expr, List<LocalQuickFix> actions) {
final PyClass containedClass = PsiTreeUtil.getParentOfType(node, PyClass.class);
final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class);
if (containedClass != null && function != null) {
final PyParameter[] parameters = function.getParameterList().getParameters();
if (parameters.length == 0) return;
final String qualifier = parameters[0].getText();
final PyDecoratorList decoratorList = function.getDecoratorList();
boolean isClassMethod = false;
if (decoratorList != null) {
for (PyDecorator decorator : decoratorList.getDecorators()) {
final PyExpression callee = decorator.getCallee();
if (callee != null && PyNames.CLASSMETHOD.equals(callee.getText())) {
isClassMethod = true;
}
}
}
for (PyTargetExpression target : containedClass.getInstanceAttributes()) {
if (!isClassMethod && Comparing.strEqual(node.getName(), target.getName())) {
actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier));
}
}
for (PyStatement statement : containedClass.getStatementList().getStatements()) {
if (statement instanceof PyAssignmentStatement) {
PyExpression lhsExpression = ((PyAssignmentStatement)statement).getLeftHandSideExpression();
if (lhsExpression != null && lhsExpression.getText().equals(expr.getText())) {
PyExpression assignedValue = ((PyAssignmentStatement)statement).getAssignedValue();
if (assignedValue instanceof PyCallExpression) {
PyType type = myTypeEvalContext.getType(assignedValue);
if (type != null && type instanceof PyClassTypeImpl) {
if (((PyCallExpression)assignedValue).isCalleeText(PyNames.PROPERTY)) {
actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier));
}
}
}
}
}
}
for (PyFunction method : containedClass.getMethods()) {
if (expr.getText().equals(method.getName())) {
actions.add(new UnresolvedReferenceAddSelfQuickFix(expr, qualifier));
}
}
}
}
private static void addAutoImportFix(PyElement node, PsiReference reference, List<LocalQuickFix> actions) {
final PsiFile file = InjectedLanguageManager.getInstance(node.getProject()).getTopLevelFile(node);
if (!(file instanceof PyFile)) return;
AutoImportQuickFix importFix = PythonReferenceImporter.proposeImportFix(node, reference);
if (importFix != null) {
if (!suppressHintForAutoImport(node, importFix) && PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) {
final AutoImportHintAction autoImportHintAction = new AutoImportHintAction(importFix);
actions.add(autoImportHintAction);
}
else {
actions.add(importFix);
}
if (ScopeUtil.getScopeOwner(node) instanceof PyFunction) {
actions.add(importFix.forLocalImport());
}
}
}
private static boolean suppressHintForAutoImport(PyElement node, AutoImportQuickFix importFix) {
// if the context doesn't look like a function call and we only found imports of functions, suggest auto-import
// as a quickfix but no popup balloon (PY-2312)
if (!isCall(node) && importFix.hasOnlyFunctions()) {
return true;
}
// if we're in a class context and the class defines a variable with the same name, offer auto-import only as quickfix,
// not as popup
PyClass containingClass = PsiTreeUtil.getParentOfType(node, PyClass.class);
if (containingClass != null && (containingClass.findMethodByName(importFix.getNameToImport(), true) != null ||
containingClass.findInstanceAttribute(importFix.getNameToImport(), true) != null)) {
return true;
}
return false;
}
private void addCreateClassFix(String refText, PsiElement element, List<LocalQuickFix> actions) {
if (refText.length() > 2 && Character.isUpperCase(refText.charAt(0)) && !refText.toUpperCase().equals(refText) &&
PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) == null) {
PsiElement anchor = element;
if (element instanceof PyQualifiedExpression) {
final PyExpression expr = ((PyQualifiedExpression)element).getQualifier();
if (expr != null) {
final PyType type = myTypeEvalContext.getType(expr);
if (type instanceof PyModuleType) {
anchor = ((PyModuleType)type).getModule();
}
else {
anchor = null;
}
}
if (anchor != null) {
actions.add(new CreateClassQuickFix(refText, anchor));
}
}
}
}
private static boolean isCall(PyElement node) {
final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(node, PyCallExpression.class);
return callExpression != null && node == callExpression.getCallee();
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) {
final PyType type = context.getType(cls);
if (type != null) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
if (results != null && !results.isEmpty()) {
return results.get(0).getElement();
}
}
return null;
}
private static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context);
if (method != null) {
return true;
}
method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context);
if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) {
return true;
}
return false;
}
private static void addPluginQuickFixes(PsiReference reference, final List<LocalQuickFix> actions) {
for (PyUnresolvedReferenceQuickFixProvider provider : Extensions.getExtensions(PyUnresolvedReferenceQuickFixProvider.EP_NAME)) {
provider.registerQuickFixes(reference, new Consumer<LocalQuickFix>() {
public void consume(LocalQuickFix localQuickFix) {
actions.add(localQuickFix);
}
});
}
}
public void highlightUnusedImports() {
final List<PsiElement> unused = collectUnusedImportElements();
for (PsiElement element : unused) {
if (element.getTextLength() > 0) {
registerProblem(element, "Unused import statement", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, new OptimizeImportsQuickFix());
}
}
}
private List<PsiElement> collectUnusedImportElements() {
if (myAllImports.isEmpty()) {
return Collections.emptyList();
}
// PY-1315 Unused imports inspection shouldn't work in python REPL console
final NameDefiner first = myAllImports.iterator().next();
if (first.getContainingFile() instanceof PyExpressionCodeFragment || PydevConsoleRunner.isInPydevConsole(first)) {
return Collections.emptyList();
}
List<PsiElement> result = new ArrayList<PsiElement>();
Set<PyImportedNameDefiner> unusedImports = new HashSet<PyImportedNameDefiner>(myAllImports);
unusedImports.removeAll(myUsedImports);
// Remove those unsed, that are reported to be skipped by extension points
final Set<PyImportedNameDefiner> unusedImportToSkip = new HashSet<PyImportedNameDefiner>();
for (final PyImportedNameDefiner unusedImport : unusedImports) {
if (importShouldBeSkippedByExtPoint(unusedImport)) { // Pass to extension points
unusedImportToSkip.add(unusedImport);
}
}
unusedImports.removeAll(unusedImportToSkip);
Set<String> usedImportNames = new HashSet<String>();
for (PsiElement usedImport : myUsedImports) {
if (usedImport instanceof NameDefiner) {
for (PyElement e : ((NameDefiner)usedImport).iterateNames()) {
usedImportNames.add(e.getName());
}
}
}
Set<PyImportStatementBase> unusedStatements = new HashSet<PyImportStatementBase>();
final PyUnresolvedReferencesInspection suppressableInspection = new PyUnresolvedReferencesInspection();
QualifiedName packageQName = null;
List<String> dunderAll = null;
// TODO: Use strategies instead of pack of "continue"
for (PyImportedNameDefiner unusedImport : unusedImports) {
if (packageQName == null) {
final PsiFile file = unusedImport.getContainingFile();
if (file instanceof PyFile) {
dunderAll = ((PyFile)file).getDunderAll();
}
if (file != null && PyUtil.isPackage(file)) {
packageQName = QualifiedNameFinder.findShortestImportableQName(file);
}
}
PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class);
if (importStatement != null && !unusedStatements.contains(importStatement) && !myUsedImports.contains(importStatement)) {
if (suppressableInspection.isSuppressedFor(importStatement)) {
continue;
}
// don't remove as unused imports in try/except statements
if (PsiTreeUtil.getParentOfType(importStatement, PyTryExceptStatement.class) != null) {
continue;
}
// Don't report conditional imports as unused
if (PsiTreeUtil.getParentOfType(unusedImport, PyIfStatement.class) != null) {
boolean isUsed = false;
for (PyElement e : unusedImport.iterateNames()) {
if (usedImportNames.contains(e.getName())) {
isUsed = true;
}
}
if (isUsed) {
continue;
}
}
PsiElement importedElement;
if (unusedImport instanceof PyImportElement) {
final PyImportElement importElement = (PyImportElement)unusedImport;
final PsiElement element = importElement.resolve();
if (element == null) {
if (importElement.getImportedQName() != null) {
//Mark import as unused even if it can't be resolved
if (areAllImportsUnused(importStatement, unusedImports)) {
result.add(importStatement);
}
else {
result.add(importElement);
}
}
continue;
}
if (dunderAll != null && dunderAll.contains(importElement.getVisibleName())) {
continue;
}
importedElement = element.getContainingFile();
}
else {
assert importStatement instanceof PyFromImportStatement;
importedElement = ((PyFromImportStatement)importStatement).resolveImportSource();
if (importedElement == null) {
continue;
}
}
if (packageQName != null && importedElement instanceof PsiFileSystemItem) {
final QualifiedName importedQName = QualifiedNameFinder.findShortestImportableQName((PsiFileSystemItem)importedElement);
if (importedQName != null && importedQName.matchesPrefix(packageQName)) {
continue;
}
}
if (unusedImport instanceof PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) {
unusedStatements.add(importStatement);
result.add(importStatement);
}
else {
result.add(unusedImport);
}
}
}
return result;
}
private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set<PyImportedNameDefiner> unusedImports) {
final PyImportElement[] elements = importStatement.getImportElements();
for (PyImportElement element : elements) {
if (!unusedImports.contains(element)) {
return false;
}
}
return true;
}
public void optimizeImports() {
final List<PsiElement> elementsToDelete = collectUnusedImportElements();
for (PsiElement element : elementsToDelete) {
if (element.isValid()) {
element.delete();
}
}
}
}
/**
* Checks if one or more extension points ask unused import to be skipped
* @param importNameDefiner unused import
* @return true of one or more asks
*/
private static boolean importShouldBeSkippedByExtPoint(@NotNull final PyImportedNameDefiner importNameDefiner) {
for (final PyUnresolvedReferenceSkipperExtPoint skipper : PyUnresolvedReferenceSkipperExtPoint.EP_NAME.getExtensions()) {
if (skipper.unusedImportShouldBeSkipped(importNameDefiner)) {
return true;
}
}
return false;
}
}