mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
+4
-25
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
+6
-30
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user