diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 78322e0e29f1..11bf8e84232b 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -363,6 +363,8 @@ INSP.NAME.unnecessary.backslash=Unnecessary backslash # PySingleQuotedDocstringInspection INSP.NAME.single.quoted.docstring=Single quoted docstring +# PyMissingConstructorInspection +INSP.NAME.missing.super.constructor=Missed call to constructor of super class # Refactoring # introduce diff --git a/python/src/com/jetbrains/python/inspections/PyMissingConstructorInspection.java b/python/src/com/jetbrains/python/inspections/PyMissingConstructorInspection.java new file mode 100644 index 000000000000..4865c607278b --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyMissingConstructorInspection.java @@ -0,0 +1,112 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.util.containers.Stack; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * User: catherine + * + * Inspection to warn if call to super constructor in class is missed + */ +public class PyMissingConstructorInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.missing.super.constructor"); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends PyInspectionVisitor { + + public Visitor(final ProblemsHolder holder) { + super(holder); + } + + @Override + public void visitPyClass(final PyClass node) { + PyClass[] superClasses = node.getSuperClasses(); + if (superClasses.length == 0) + return; + Set superNames = new HashSet(); + if (node.isNewStyleClass()) + superNames.add(PyNames.SUPER); + for (PyClass cl : superClasses) { + if (!PyNames.OBJECT.equals(cl.getName())) + superNames.add(cl.getName()); + } + if (superClasses.length == 1 && PyNames.OBJECT.equals(superClasses[0].getName())) + return; + + PyFunction initMethod = node.findMethodByName(PyNames.INIT, false); + if (initMethod != null) { + if (hasConstructorCall(initMethod, superNames)) + return; + registerProblem(initMethod.getNameIdentifier(), "Call to constructor of super class is missed"); + } + } + + private static boolean hasConstructorCall(PyFunction initMethod, Set superNames) { + Stack stack = new Stack(); + PyStatementList statementList = initMethod.getStatementList(); + boolean hasConstructor = false; + if (statementList != null) { + for (PyStatement st : statementList.getStatements()) { + stack.push(st); + while (!stack.isEmpty()) { + PsiElement e = stack.pop(); + if (e instanceof PyExpressionStatement) { + PyExpression expression = ((PyExpressionStatement)e).getExpression(); + if (expression instanceof PyCallExpression) { + if (isConstructorCall((PyCallExpression)expression, superNames)) + hasConstructor = true; + } + } + for (PsiElement psiElement : e.getChildren()) { + stack.push(psiElement); + } + } + } + } + return hasConstructor; + } + + private static boolean isConstructorCall(PyCallExpression expression, Set superNames) { + PyExpression callee = expression.getCallee(); + if (callee instanceof PyQualifiedExpression) { + PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier(); + if (qualifier != null) { + String tmp = ""; + if (qualifier instanceof PyCallExpression) { + PyExpression innerCallee = ((PyCallExpression)qualifier).getCallee(); + if (innerCallee != null) + tmp = innerCallee.getName(); + } + else + tmp = qualifier.getText(); + if (superNames.contains(tmp)) { + if(PyNames.INIT.equals(callee.getName())){ + return true; + } + } + } + } + return false; + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java index a36ce50df6a2..7f619eb2d23c 100644 --- a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java +++ b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java @@ -56,6 +56,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider { PyListCreationInspection.class, PyUnnecessaryBackslashInspection.class, PySingleQuotedDocstringInspection.class, + PyMissingConstructorInspection.class, }; } } diff --git a/python/testData/inspections/PyMissingConstructorInspection/test.py b/python/testData/inspections/PyMissingConstructorInspection/test.py new file mode 100644 index 000000000000..28d0cdb3c894 --- /dev/null +++ b/python/testData/inspections/PyMissingConstructorInspection/test.py @@ -0,0 +1,38 @@ +class A(object): + def __init__(self): + pass + +class AA(A): + def __init__(self): + A.__init__(self) + print("Constructor AA was called") + +class B(A): + def __init__(self): + print("Constructor B was called") + +class C(B): + def __init__(self): + super(C, self).__init__() + print("Constructor C was called") + + +class A: + def __init__(self): + print("A __init__") + + +class B(A): + def __init__(self): + A.__init__(self) + print("Constructor B was called") + +class C(B): + def __init__(self): + print("Constructor C was called") + +class D(A): + def __init__(self): + if True: + A.__init__(self) + print("Constructor D was called") \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 8c13b1f54e11..9810a9cd7f2a 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -312,4 +312,14 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { public void testPySingleQuotedDocstringInspection() { //PY-1445 doHighlightingTest(PySingleQuotedDocstringInspection.class); } + + public void testPyMissingConstructorInspection() { //PY-3055 + setLanguageLevel(LanguageLevel.PYTHON27); + doHighlightingTest(PyMissingConstructorInspection.class); + } + + public void testPyMissingConstructorInspection30() { //PY-3055 + setLanguageLevel(LanguageLevel.PYTHON30); + doHighlightingTest(PyMissingConstructorInspection.class); + } }