implemented W0232: Class has no __init__ method

extracted logic for "add element to statement list" into PyUtil
reused in PyMoveAttributeToInitQuickFix and PyClassHasNoInitInspection

reused AddMethodQuickFix in PyClassHasNoInitInspection
This commit is contained in:
Ekaterina Tuzova
2013-03-22 14:13:18 +04:00
parent e6d9c74ab2
commit eafde14771
22 changed files with 228 additions and 85 deletions
@@ -317,6 +317,7 @@
<localInspection language="Python" shortName="PyPackageRequirementsInspection" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.requirements" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyPackageRequirementsInspection"/>
<localInspection language="Python" shortName="PyPep8Inspection" displayName="PEP 8 coding style violation" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyPep8Inspection"/>
<localInspection language="Python" shortName="PyAttributeOutsideInitInspection" displayName="Instance attribute defined outside _init_" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyAttributeOutsideInitInspection"/>
<localInspection language="Python" shortName="PyClassHasNoInitInspection" displayName="Class has no __init__ method" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyClassHasNoInitInspection"/>
<localInspection language="Python" shortName="PyProtectedMemberInspection" displayName="Access to a protected member of a class" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyProtectedMemberInspection"/>
<localInspection language="Python" shortName="PyDocstringTypesInspection" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.docstring.types" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyDocstringTypesInspection"/>
<localInspection language="Python" shortName="PyShadowingBuiltinsInspection" displayName="Shadowing built-ins" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyShadowingBuiltinsInspection"/>
@@ -471,6 +471,11 @@ INSP.NAME.decorator.outside.class=Class specific decorator on method outside cla
# PyPackageRequirementsInspection
INSP.NAME.requirements=Package requirements
# PyClassHasNoInitInspection
INSP.NAME.class.has.no.init=Class has no __init__ method
INSP.class.has.no.init=Class has no __init__ method
INSP.parent.$0.has.no.init=Parent {0} has no __init__ method
# Refactoring
# introduce
@@ -51,7 +51,7 @@ public class PyAttributeOutsideInitInspection extends PyInspection {
if (containingClass == null) return;
Map<String, PyTargetExpression> attributesInInit = new HashMap<String, PyTargetExpression>();
final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, true);
final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, false);
if (initMethod != null)
PyClassImpl.collectInstanceAttributes(initMethod, attributesInInit);
@@ -0,0 +1,57 @@
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.inspections.quickfix.AddMethodQuickFix;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.types.PyClassTypeImpl;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: ktisha
* See pylint W0232
*/
public class PyClassHasNoInitInspection extends PyInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return PyBundle.message("INSP.NAME.class.has.no.init");
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
private static class Visitor extends PyInspectionVisitor {
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyClass(PyClass node) {
final PyFunction init = node.findMethodByName(PyNames.INIT, false);
if (init == null) {
registerProblem(node.getNameIdentifier(), PyBundle.message("INSP.class.has.no.init"),
new AddMethodQuickFix("__init__", new PyClassTypeImpl(node, false), false));
}
for (PyClass ancestor : node.iterateAncestorClasses()) {
final PyFunction ancestorInit = ancestor.findMethodByName(PyNames.INIT, false);
if (ancestorInit == null) {
registerProblem(node.getNameIdentifier(), PyBundle.message("INSP.parent.$0.has.no.init", ancestor.getName()),
new AddMethodQuickFix("__init__", new PyClassTypeImpl(ancestor, false), false));
}
}
}
}
}
@@ -684,7 +684,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
return true;
}
final String docString = cls.getDocStringValue();
if (docString != null && docString.indexOf("@DynamicAttrs") != -1) {
if (docString != null && docString.contains("@DynamicAttrs")) {
return true;
}
}
@@ -697,7 +697,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
PyClass cls = ((PyClassType)qtype).getPyClass();
if (!PyBuiltinCache.getInstance(element).hasInBuiltins(cls)) {
if (element.getParent() instanceof PyCallExpression) {
actions.add(new AddMethodQuickFix(refText, (PyClassType)qtype));
actions.add(new AddMethodQuickFix(refText, (PyClassType)qtype, true));
}
else if (!(reference instanceof PyOperatorReference)) {
actions.add(new AddFieldQuickFix(refText, cls, "None"));
@@ -29,11 +29,14 @@ import static com.jetbrains.python.psi.PyUtil.sure;
public class AddMethodQuickFix implements LocalQuickFix {
private PyClassType myQualifierType;
private final boolean myReplaceUsage;
private String myIdentifier;
public AddMethodQuickFix(String identifier, PyClassType qualifierType) {
public AddMethodQuickFix(String identifier, PyClassType qualifierType,
boolean replaceUsage) {
myIdentifier = identifier;
myQualifierType = qualifierType;
myReplaceUsage = replaceUsage;
}
@NotNull
@@ -62,10 +65,12 @@ public class AddMethodQuickFix implements LocalQuickFix {
PyFunctionBuilder builder = new PyFunctionBuilder(item_name);
PsiElement pe = problem_elt.getParent();
String deco_name = null; // set to non-null to add a decorator
sure(pe instanceof PyCallExpression);
PyArgumentList arglist = ((PyCallExpression)pe).getArgumentList();
sure(arglist);
final PyExpression[] args = arglist.getArguments();
PyExpression[] args = new PyExpression[0];
if (pe instanceof PyCallExpression) {
PyArgumentList arglist = ((PyCallExpression)pe).getArgumentList();
sure(arglist);
args = arglist.getArguments();
}
boolean made_instance = false;
if (call_by_class) {
if (args.length > 0) {
@@ -107,17 +112,10 @@ public class AddMethodQuickFix implements LocalQuickFix {
PyDecoratorList deco_list = generator.createFromText(LanguageLevel.getDefault(), PyDecoratorList.class, "@" + deco_name + "\ndef foo(): pass", new int[]{0, 0});
meth.addBefore(deco_list, meth.getFirstChild()); // in the very beginning
}
final PsiElement first_stmt = cls_stmt_list.getFirstChild();
if (first_stmt == cls_stmt_list.getLastChild() && first_stmt instanceof PyPassStatement) {
// replace the lone 'pass'
meth = (PyFunction) first_stmt.replace(meth);
}
else {
// add ourselves to the bottom
meth = (PyFunction) cls_stmt_list.add(meth);
}
showTemplateBuilder(meth);
meth = (PyFunction)PyUtil.addElementToStatementList(meth, cls_stmt_list);
if (myReplaceUsage)
showTemplateBuilder(meth);
}
catch (IncorrectOperationException ignored) {
// we failed. tell about this
@@ -138,7 +136,9 @@ public class AddMethodQuickFix implements LocalQuickFix {
}
);
builder.replaceElement(method.getStatementList(), PyNames.PASS);
final PyStatementList statementList = method.getStatementList();
if (statementList == null) return;
builder.replaceElement(statementList, PyNames.PASS);
builder.run();
}
@@ -46,42 +46,21 @@ public class PyMoveAttributeToInitQuickFix implements LocalQuickFix {
}
private static boolean addDefinition(PsiElement copy, PyClass containingClass) {
PyFunction init = containingClass.findMethodByName(PyNames.INIT, true);
PyFunction init = containingClass.findMethodByName(PyNames.INIT, false);
if (init == null) {
final PyStatementList classStatementList = containingClass.getStatementList();
final PyStatement[] statements = classStatementList.getStatements();
init = PyElementGenerator.getInstance(containingClass.getProject()).createFromText(LanguageLevel.forElement(containingClass),
PyFunction.class,
"def __init__(self):\n\t" +
copy.getText());
if (statements.length > 0) {
final PyStatement statement = statements[0];
if (statement instanceof PyExpressionStatement &&
((PyExpressionStatement)statement).getExpression() == containingClass.getDocStringExpression())
classStatementList.addAfter(init, statement);
else
classStatementList.addBefore(init, statement);
}
else {
classStatementList.add(init);
}
PyUtil.addElementToStatementList(init, classStatementList);
return true;
}
final PyStatementList statementList = init.getStatementList();
if (statementList == null) return false;
final PyStatement[] statements = statementList.getStatements();
if (statements.length == 1) {
final PyStatement firstStatement = statements[0];
if (firstStatement instanceof PyPassStatement) {
firstStatement.replace(copy);
}
else
statementList.addAfter(copy, statements[statements.length - 1]);
}
else
statementList.addAfter(copy, statements[statements.length - 1]);
PyUtil.addElementToStatementList(copy, statementList);
return true;
}
@@ -1202,5 +1202,30 @@ public class PyUtil {
return element instanceof PsiWhiteSpace ? null : element;
}
public static PsiElement addElementToStatementList(@NotNull PsiElement element, @NotNull PyStatementList statementList) {
final PsiElement firstChild = statementList.getFirstChild();
if (firstChild == statementList.getLastChild() && firstChild instanceof PyPassStatement) {
element = firstChild.replace(element);
}
else {
final PyStatement[] statements = statementList.getStatements();
String name = element instanceof PsiNamedElement ? ((PsiNamedElement)element).getName() : "";
if (PyNames.INIT.equals(name) && statements.length > 0) {
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(statementList, PyDocStringOwner.class);
final PyStatement firstStatement = statements[0];
if (docStringOwner != null && firstStatement instanceof PyExpressionStatement &&
((PyExpressionStatement)firstStatement).getExpression() == docStringOwner.getDocStringExpression()) {
element = statementList.addAfter(element, firstStatement);
}
else
element = statementList.addBefore(element, firstStatement);
}
else {
element = statementList.add(element);
}
}
return element;
}
}
@@ -0,0 +1,4 @@
__author__ = 'ktisha'
class <weak_warning descr="Class has no __init__ method">A</weak_warning>:
def foo(self):
self.b = 1
@@ -0,0 +1,9 @@
__author__ = 'ktisha'
class <weak_warning descr="Class has no __init__ method">A</weak_warning>:
def foo(self):
self.b = 1
class <weak_warning descr="Parent A has no __init__ method">B</weak_warning>(A):
def __init__(self):
self.b = 2
@@ -0,0 +1,7 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self._a = 1
def foo(self):
self.b = 1
@@ -0,0 +1,4 @@
__author__ = 'ktisha'
class <caret>A(object):
def foo(self):
self.b = 1
@@ -0,0 +1,7 @@
__author__ = 'ktisha'
class A(object):
def __init__(self):
pass
def foo(self):
self.b = 1
@@ -115,16 +115,6 @@ public class PyQuickFixTest extends PyTestCase {
true, true);
}
public void testAddMethodFromInstance() {
doInspectionTest("AddMethodFromInstance.py", PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A"),
true, true);
}
public void testAddMethodFromMethod() {
doInspectionTest("AddMethodFromMethod.py", PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A"),
true, true);
}
public void testRemoveTrailingSemicolon() {
doInspectionTest("RemoveTrailingSemicolon.py", PyTrailingSemicolonInspection.class, PyBundle.message("QFIX.remove.trailing.semicolon"),
true, true);
@@ -0,0 +1,27 @@
package com.jetbrains.python.inspections;
import com.jetbrains.python.fixtures.PyTestCase;
/**
* User: ktisha
*/
public class PyClassHasNoInitInspectionTest extends PyTestCase {
public void testClass() {
doTest();
}
public void testTrueNegative() {
doTest();
}
public void testParentClass() {
doTest();
}
private void doTest() {
myFixture.configureByFile("inspections/PyClassHasNoInitInspection/" + getTestName(true) + ".py");
myFixture.enableInspections(PyClassHasNoInitInspection.class);
myFixture.checkHighlighting(false, false, true);
}
}
@@ -0,0 +1,24 @@
package com.jetbrains.python.quickFixes;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.inspections.PyClassHasNoInitInspection;
import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection;
/**
* User: ktisha
*/
public class AddMethodQuickFixTest extends PyQuickFixTestCase {
public void testAddInit() {
doInspectionTest(PyClassHasNoInitInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "__init__", "A"));
}
public void testAddMethodFromInstance() {
doInspectionTest(PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A"));
}
public void testAddMethodFromMethod() {
doInspectionTest(PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A"));
}
}
@@ -1,57 +1,33 @@
package com.jetbrains.python.quickFixes;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonTestUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.inspections.PyAttributeOutsideInitInspection;
import org.jetbrains.annotations.NonNls;
/**
* User: ktisha
*/
@TestDataPath("$CONTENT_ROOT/../testData/quickFixes/PyMoveAttributeToInitQuickFixTest")
public class PyMoveAttributeToInitQuickFixTest extends PyTestCase {
public class PyMoveAttributeToInitQuickFixTest extends PyQuickFixTestCase {
public void testMoveToInit() {
doInspectionTest(PyAttributeOutsideInitInspection.class, true);
doInspectionTest(PyAttributeOutsideInitInspection.class, PyBundle.message("QFIX.move.attribute"));
}
public void testCreateInit() {
doInspectionTest(PyAttributeOutsideInitInspection.class, true);
doInspectionTest(PyAttributeOutsideInitInspection.class, PyBundle.message("QFIX.move.attribute"));
}
public void testAddPass() {
doInspectionTest(PyAttributeOutsideInitInspection.class, true);
doInspectionTest(PyAttributeOutsideInitInspection.class, PyBundle.message("QFIX.move.attribute"));
}
public void testRemovePass() {
doInspectionTest(PyAttributeOutsideInitInspection.class, true);
doInspectionTest(PyAttributeOutsideInitInspection.class, PyBundle.message("QFIX.move.attribute"));
}
public void testSkipDocstring() {
doInspectionTest(PyAttributeOutsideInitInspection.class, true);
}
@Override
@NonNls
protected String getTestDataPath() {
return PythonTestUtil.getTestDataPath() + "/quickFixes/PyMoveAttributeToInitQuickFixTest";
}
protected void doInspectionTest(final Class inspectionClass,
boolean applyFix) {
final String testFileName = getTestName(true);
myFixture.enableInspections(inspectionClass);
myFixture.configureByFile(testFileName + ".py");
myFixture.checkHighlighting(true, false, false);
final IntentionAction intentionAction = myFixture.findSingleIntention(PyBundle.message("QFIX.move.attribute"));
assertNotNull(intentionAction);
if (applyFix) {
myFixture.launchAction(intentionAction);
myFixture.checkResultByFile(testFileName + "_after.py", true);
}
doInspectionTest(PyAttributeOutsideInitInspection.class, PyBundle.message("QFIX.move.attribute"));
}
}
@@ -0,0 +1,28 @@
package com.jetbrains.python.quickFixes;
import com.intellij.codeInsight.intention.IntentionAction;
import com.jetbrains.python.PythonTestUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import org.jetbrains.annotations.NonNls;
/**
* User: ktisha
*/
public abstract class PyQuickFixTestCase extends PyTestCase {
@Override
@NonNls
protected String getTestDataPath() {
return PythonTestUtil.getTestDataPath() + "/quickFixes/" + getClass().getSimpleName();
}
protected void doInspectionTest(final Class inspectionClass, final String hint) {
final String testFileName = getTestName(true);
myFixture.enableInspections(inspectionClass);
myFixture.configureByFile(testFileName + ".py");
myFixture.checkHighlighting(true, false, false);
final IntentionAction intentionAction = myFixture.findSingleIntention(hint);
assertNotNull(intentionAction);
myFixture.launchAction(intentionAction);
myFixture.checkResultByFile(testFileName + "_after.py", true);
}
}