Partially closes PY-1438, adds call-by-class first argument type check.

This commit is contained in:
Dmitry Cheryasov
2010-09-22 17:22:17 +03:00
parent c02c196855
commit 92bb78dff8
5 changed files with 144 additions and 0 deletions
@@ -270,6 +270,12 @@ INSP.setter.signature.advice=Setter signature should be (self, value)
INSP.deleter.signature.advice=Deleter signature should be (self)
INSP.accessor.first.param.is.$0=First parameter of an accessor is usually called ''{0}''
# PyCallByClassInspection
INSP.NAME.different.class.call=Calling a method by class using an instance of a different class
INSP.instance.of.$0.excpected=An instance of {0} expected, not the class itself
INSP.passing.$0.instead.of.$1=Passing {0} instead of {1}. Is this intentional?
# Refactoring
# introduce
refactoring.introduce.name.error=Incorrect name
@@ -0,0 +1,114 @@
package com.jetbrains.python.inspections;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import static com.jetbrains.python.psi.PyFunction.Flag.STATICMETHOD;
import static com.jetbrains.python.psi.PyFunction.Flag.CLASSMETHOD;
/**
* Checks for for calls like X.method(y,...), where y is not an instance of X.
* <br/>
* User: dcheryasov
* Date: Sep 22, 2010 12:21:44 PM
*/
public class PyCallByClassInspection extends PyInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return PyBundle.message("INSP.NAME.different.class.call");
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@NotNull
@Override
public HighlightDisplayLevel getDefaultLevel() {
return HighlightDisplayLevel.INFO;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
private static class Visitor extends PyInspectionVisitor {
public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyCallExpression(PyCallExpression call) {
PyExpression callee = call.getCallee();
if (callee instanceof PyQualifiedExpression) {
PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier();
if (qualifier != null) {
PyType qual_type = myTypeEvalContext.getType(qualifier);
if (qual_type instanceof PyClassType) {
final PyClassType qual_class_type = (PyClassType)qual_type;
if (qual_class_type.isDefinition()) {
PyClass qual_class = qual_class_type.getPyClass();
if (qual_class != null) {
final PyArgumentList arglist = call.getArgumentList();
if (arglist != null) {
PyArgumentList.AnalysisResult analysis = arglist.analyzeCall(myTypeEvalContext);
final PyCallExpression.PyMarkedCallee markedCallee = analysis.getMarkedCallee();
if (markedCallee != null && !markedCallee.getFlags().contains(STATICMETHOD)) {
PyParameter[] params = markedCallee.getCallable().getParameterList().getParameters();
if (params.length > 0 && params[0] instanceof PyNamedParameter) {
PyNamedParameter first_param = (PyNamedParameter)params[0];
for (Map.Entry<PyExpression, PyNamedParameter> entry : analysis.getPlainMappedParams().entrySet()) {
// we ignore *arg and **arg which we cannot analyze
if (entry.getValue() == first_param) {
PyExpression first_arg = entry.getKey();
assert first_arg != null;
PyType first_arg_type = myTypeEvalContext.getType(first_arg);
if (first_arg_type instanceof PyClassType) {
final PyClassType first_arg_class_type = (PyClassType)first_arg_type;
if (first_arg_class_type.isDefinition() && !markedCallee.getFlags().contains(CLASSMETHOD)) {
registerProblem(
first_arg,
PyBundle.message("INSP.instance.of.$0.excpected", qual_class.getQualifiedName())
);
}
PyClass first_arg_class = first_arg_class_type.getPyClass();
if (first_arg_class != null && first_arg_class != qual_class) {
registerProblem(
first_arg,
PyBundle.message(
"INSP.passing.$0.instead.of.$1",
first_arg_class.getQualifiedName(), qual_class.getQualifiedName()
)
);
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
@@ -45,6 +45,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider {
PyPropertyDefinitionInspection.class,
PyInconsistentIndentationInspection.class,
PyNestedDecoratorsInspection.class,
PyCallByClassInspection.class,
};
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
<problem>
<file>badarglist.py</file>
<line>7</line>
<description>Passing src.badarglist.A instead of src.badarglist.Z. Is this intentional?</description>
</problem>
<problem>
<file>badarglist.py</file>
<line>8</line>
<description>An instance of src.badarglist.Z is expected, not the class itself</description>
</problem>
<problem>
<file>badarglist.py</file>
<line>9</line>
<description>An instance of src.badarglist.Z is expected, not the class itself</description>
<description>Passing src.badarglist.A instead of src.badarglist.Z. Is this intentional?</description>
</problem>
</problems>
@@ -218,6 +218,10 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase {
doTest(getTestName(false), inspection);
}
public void testPyCallByClassInspection() throws Exception {
doTest(getTestName(false), new PyCallByClassInspection());
}
public void testPyCallingNonCallableInspection() throws Exception {
doHighlightingTest(PyCallingNonCallableInspection.class, LanguageLevel.PYTHON26);
}