All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.inspections.PyPropertyDefinitionInspection Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.inspections;

import com.google.common.collect.ImmutableList;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.Processor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.inspections.quickfix.PyUpdatePropertySignatureQuickFix;
import com.jetbrains.python.inspections.quickfix.RenameParameterQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyNoneType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeChecker;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Checks that arguments to property() and @property and friends are ok.
 * 
* User: dcheryasov * Date: Jun 30, 2010 2:53:05 PM */ public class PyPropertyDefinitionInspection extends PyInspection { @Nls @NotNull public String getDisplayName() { return PyBundle.message("INSP.NAME.property.definition"); } private static final ImmutableList SUFFIXES = ImmutableList.of(PyNames.SETTER, PyNames.DELETER); @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { return new Visitor(holder, session); } public static class Visitor extends PyInspectionVisitor { private LanguageLevel myLevel; private List myStringClasses; private PyFunction myOneParamFunction; private PyFunction myTwoParamFunction; // arglist with two args, 'self' and 'value' public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) { super(holder, session); PsiFile psiFile = session.getFile(); // save us continuous checks for level, module, stc myLevel = LanguageLevel.forElement(psiFile); // string classes final List stringClasses = new ArrayList(2); final PyBuiltinCache builtins = PyBuiltinCache.getInstance(psiFile); PyClass cls = builtins.getClass("str"); if (cls != null) stringClasses.add(cls); cls = builtins.getClass("unicode"); if (cls != null) stringClasses.add(cls); myStringClasses = stringClasses; // reference signatures PyClass objectClass = builtins.getClass("object"); if (objectClass != null) { final PyFunction methodRepr = objectClass.findMethodByName("__repr__", false); if (methodRepr != null) myOneParamFunction = methodRepr; final PyFunction methodDelattr = objectClass.findMethodByName("__delattr__", false); if (methodDelattr != null) myTwoParamFunction = methodDelattr; } } @Override public void visitPyFile(PyFile node) { super.visitPyFile(node); } @Override public void visitPyClass(final PyClass node) { super.visitPyClass(node); // check property() and @property node.scanProperties(new Processor() { @Override public boolean process(Property property) { PyTargetExpression target = property.getDefinitionSite(); if (target != null) { // target = property(); args may be all funny PyCallExpression call = (PyCallExpression)target.findAssignedValue(); assert call != null : "Property has a null call assigned to it"; final PyArgumentList arglist = call.getArgumentList(); assert arglist != null : "Property call has null arglist"; CallArgumentsMapping analysis = arglist.analyzeCall(getResolveContext()); // we assume fget, fset, fdel, doc names for (Map.Entry entry : analysis.getPlainMappedParams().entrySet()) { final String paramName = entry.getValue().getName(); PyExpression argument = PyUtil.peelArgument(entry.getKey()); checkPropertyCallArgument(paramName, argument, node.getContainingFile()); } } else { // @property; we only check getter, others are checked by visitPyFunction // getter is always present with this form final PyCallable callable = property.getGetter().valueOrNull(); if (callable instanceof PyFunction) { checkGetter(callable, getFunctionMarkingElement((PyFunction)callable)); } } return false; // always want more } }, false); } private void checkPropertyCallArgument(String paramName, PyExpression argument, PsiFile containingFile) { assert argument != null : "Parameter mapped to null argument"; PyCallable callable = null; if (argument instanceof PyReferenceExpression) { final PsiPolyVariantReference reference = ((PyReferenceExpression)argument).getReference(getResolveContext()); PsiElement resolved = reference.resolve(); if (resolved instanceof PyCallable) { callable = (PyCallable)resolved; } else { reportNonCallableArg(resolved, argument); return; } } else if (argument instanceof PyLambdaExpression) { callable = (PyLambdaExpression)argument; } else if (!"doc".equals(paramName)) { reportNonCallableArg(argument, argument); return; } if (callable != null && callable.getContainingFile() != containingFile) { return; } if ("fget".equals(paramName)) { checkGetter(callable, argument); } else if ("fset".equals(paramName)) { checkSetter(callable, argument); } else if ("fdel".equals(paramName)) { checkDeleter(callable, argument); } else if ("doc".equals(paramName)) { PyType type = myTypeEvalContext.getType(argument); if (!(type instanceof PyClassType && myStringClasses.contains(((PyClassType)type).getPyClass()))) { registerProblem(argument, PyBundle.message("INSP.doc.param.should.be.str")); } } } private void reportNonCallableArg(PsiElement resolved, PsiElement element) { if (resolved instanceof PySubscriptionExpression || resolved instanceof PyNoneLiteralExpression) { return; } if (PyNames.NONE.equals(element.getText())) { return; } if (resolved instanceof PyTypedElement) { final PyType type = myTypeEvalContext.getType((PyTypedElement)resolved); final Boolean isCallable = PyTypeChecker.isCallable(type); if (isCallable != null && !isCallable) { registerProblem(element, PyBundle.message("INSP.strange.arg.want.callable")); } } } @Override public void visitPyFunction(PyFunction node) { super.visitPyFunction(node); if (myLevel.isAtLeast(LanguageLevel.PYTHON26)) { // check @foo.setter and @foo.deleter PyClass cls = node.getContainingClass(); if (cls != null) { final PyDecoratorList decos = node.getDecoratorList(); if (decos != null) { String name = node.getName(); for (PyDecorator deco : decos.getDecorators()) { final QualifiedName qName = deco.getQualifiedName(); if (qName != null) { List nameParts = qName.getComponents(); if (nameParts.size() == 2) { final int suffixIndex = SUFFIXES.indexOf(nameParts.get(1)); if (suffixIndex >= 0) { if (Comparing.equal(name, nameParts.get(0))) { // names are ok, what about signatures? PsiElement markable = getFunctionMarkingElement(node); if (suffixIndex == 0) { checkSetter(node, markable); } else { checkDeleter(node, markable); } } else { registerProblem(deco, PyBundle.message("INSP.func.property.name.mismatch")); } } } } } } } } } @Nullable private static PsiElement getFunctionMarkingElement(PyFunction node) { if (node == null) return null; final ASTNode nameNode = node.getNameNode(); PsiElement markable = node; if (nameNode != null) markable = nameNode.getPsi(); return markable; } private void checkGetter(PyCallable callable, PsiElement beingChecked) { if (callable != null) { checkOneParameter(callable, beingChecked, true); checkReturnValueAllowed(callable, beingChecked, true, PyBundle.message("INSP.getter.return.smth")); } } private void checkSetter(PyCallable callable, PsiElement beingChecked) { if (callable != null) { // signature: at least two params, more optionals ok; first arg 'self' final PyParameterList paramList = callable.getParameterList(); if (myTwoParamFunction != null && !PyUtil.isSignatureCompatibleTo(callable, myTwoParamFunction, myTypeEvalContext)) { registerProblem(beingChecked, PyBundle.message("INSP.setter.signature.advice"), new PyUpdatePropertySignatureQuickFix(true)); } checkForSelf(paramList); // no explicit return type checkReturnValueAllowed(callable, beingChecked, false, PyBundle.message("INSP.setter.should.not.return")); } } private void checkDeleter(PyCallable callable, PsiElement beingChecked) { if (callable != null) { checkOneParameter(callable, beingChecked, false); checkReturnValueAllowed(callable, beingChecked, false, PyBundle.message("INSP.deleter.should.not.return")); } } private void checkOneParameter(PyCallable callable, PsiElement beingChecked, boolean isGetter) { final PyParameterList parameterList = callable.getParameterList(); if (myOneParamFunction != null && !PyUtil.isSignatureCompatibleTo(callable, myOneParamFunction, myTypeEvalContext)) { if (isGetter) { registerProblem(beingChecked, PyBundle.message("INSP.getter.signature.advice"), new PyUpdatePropertySignatureQuickFix(false)); } else { registerProblem(beingChecked, PyBundle.message("INSP.deleter.signature.advice"), new PyUpdatePropertySignatureQuickFix(false)); } } checkForSelf(parameterList); } private void checkForSelf(PyParameterList paramList) { PyParameter[] parameters = paramList.getParameters(); final PyClass cls = PsiTreeUtil.getParentOfType(paramList, PyClass.class); if (cls != null && cls.isSubclass("type")) return; if (parameters.length > 0 && !PyNames.CANONICAL_SELF.equals(parameters[0].getName())) { registerProblem( parameters[0], PyBundle.message("INSP.accessor.first.param.is.$0", PyNames.CANONICAL_SELF), ProblemHighlightType.WEAK_WARNING, null, new RenameParameterQuickFix(PyNames.CANONICAL_SELF)); } } private void checkReturnValueAllowed(PyCallable callable, PsiElement beingChecked, boolean allowed, String message) { // TODO: use a real flow analysis to check all exit points boolean hasReturns; if (callable instanceof PyFunction) { final PsiElement[] returnStatements = PsiTreeUtil.collectElements(callable, new PsiElementFilter() { @Override public boolean isAccepted(PsiElement element) { return (element instanceof PyReturnStatement && ((PyReturnStatement)element).getExpression() != null) || (element instanceof PyYieldExpression); } }); hasReturns = returnStatements.length > 0; } else { final PyType type = myTypeEvalContext.getReturnType(callable); hasReturns = !(type instanceof PyNoneType); } if (allowed ^ hasReturns) { if (allowed && callable instanceof PyFunction) { if (PyUtil.isDecoratedAsAbstract(((PyFunction)callable))) { return; } // one last chance: maybe there's no return but a 'raise' statement, see PY-4043, PY-5048 PyStatementList statementList = ((PyFunction)callable).getStatementList(); for (PyStatement stmt : statementList.getStatements()) { if (stmt instanceof PyRaiseStatement) { return; } } } registerProblem(beingChecked, message); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy