From 6c0fe5e92a2218dc0f7a3860059723a6622c06a1 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 21 Mar 2013 15:18:33 +0400 Subject: [PATCH 01/10] implemented W0201 pylint inspection Attribute %r defined outside __init__ --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 4 ++ .../PyAttributeOutsideInitInspection.java | 67 +++++++++++++++++++ .../python/psi/impl/PyClassImpl.java | 2 +- .../trueNegative.py | 9 +++ .../truePositive.py | 8 +++ .../PyAttributeOutsideInitInspectionTest.java | 23 +++++++ 7 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java create mode 100644 python/testData/inspections/PyAttributeOutsideInitInspection/trueNegative.py create mode 100644 python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyAttributeOutsideInitInspectionTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 57b8bf8b71c5..429afe953c8a 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -316,6 +316,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 60fda20373fa..c4573b17bf21 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -431,6 +431,10 @@ INSP.NAME.augment.assignment=Assignment can be replaced with augmented assignmen # PyChainedComparsonsInspection INSP.NAME.chained.comparisons=Chained comparisons can be simplified +# PyAttributeOutsideInitInspection +INSP.NAME.attribute.outside.init=Instance attribute defined outside _init_ +INSP.attribute.$0.outside.init=Instance attribute {0} defined outside _init_ + # PyArgumentEqualDefaultInspection INSP.NAME.argument.equal.default=Argument passed to function is equal to default parameter value INSP.argument.equals.to.default=Argument equals to default parameter value diff --git a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java new file mode 100644 index 000000000000..641c244ed72a --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java @@ -0,0 +1,67 @@ +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.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyTargetExpression; +import com.jetbrains.python.psi.impl.PyClassImpl; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * User: ktisha + * + * Inspection to detect situations, where instance attribute + * defined outside __init__ function + */ +public class PyAttributeOutsideInitInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.attribute.outside.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 visitPyFunction(PyFunction node) { + final PyClass containingClass = node.getContainingClass(); + if (containingClass == null) return; + + Map attributesInInit = new HashMap(); + final PyFunction initOrNew = containingClass.findInitOrNew(true); + if (initOrNew != null) + PyClassImpl.collectInstanceAttributes(initOrNew, attributesInInit); + + Map attributes = new HashMap(); + PyClassImpl.collectInstanceAttributes(node, attributes); + + for (Map.Entry attribute : attributes.entrySet()) { + if (!attributesInInit.containsKey(attribute.getKey())) { + registerProblem(attribute.getValue(), PyBundle.message("INSP.attribute.$0.outside.init", attribute.getKey())); + } + } + } + + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index eabaa37e163a..d96a641f3e75 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -929,7 +929,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement return new ArrayList(expressions); } - private static void collectInstanceAttributes(@NotNull PyFunction method, @NotNull final Map result) { + public static void collectInstanceAttributes(@NotNull PyFunction method, @NotNull final Map result) { final PyParameter[] params = method.getParameterList().getParameters(); if (params.length == 0) { return; diff --git a/python/testData/inspections/PyAttributeOutsideInitInspection/trueNegative.py b/python/testData/inspections/PyAttributeOutsideInitInspection/trueNegative.py new file mode 100644 index 000000000000..e0961ac41062 --- /dev/null +++ b/python/testData/inspections/PyAttributeOutsideInitInspection/trueNegative.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self.a = 1 + self.b = 1 + + def foo(self): + self.b = 1 diff --git a/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py b/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py new file mode 100644 index 000000000000..dba1dea38214 --- /dev/null +++ b/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self.a = 1 + + def foo(self): + self.b= 1 diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAttributeOutsideInitInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAttributeOutsideInitInspectionTest.java new file mode 100644 index 000000000000..45bddc738a5c --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyAttributeOutsideInitInspectionTest.java @@ -0,0 +1,23 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyAttributeOutsideInitInspectionTest extends PyTestCase { + + public void testTruePositive() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyAttributeOutsideInitInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyAttributeOutsideInitInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} From d95b3434654beee70de321b3d6bac43642569f4e Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 21 Mar 2013 15:49:23 +0400 Subject: [PATCH 02/10] implemented W0212 pylint inspection Access to a protected member %s of a client class --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 4 ++ .../PyProtectedMemberInspection.java | 65 +++++++++++++++++++ .../trueNegative.py | 15 +++++ .../truePositive.py | 11 ++++ .../truePositiveInClass.py | 12 ++++ .../PyProtectedMemberInspectionTest.java | 27 ++++++++ 7 files changed, 135 insertions(+) create mode 100644 python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java create mode 100644 python/testData/inspections/PyProtectedMemberInspection/trueNegative.py create mode 100644 python/testData/inspections/PyProtectedMemberInspection/truePositive.py create mode 100644 python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 429afe953c8a..7f2cbd10ee6d 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -317,6 +317,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index c4573b17bf21..47cf202a2c2a 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -435,6 +435,10 @@ INSP.NAME.chained.comparisons=Chained comparisons can be simplified INSP.NAME.attribute.outside.init=Instance attribute defined outside _init_ INSP.attribute.$0.outside.init=Instance attribute {0} defined outside _init_ +# PyProtectedMemberInspection +INSP.NAME.protected.member.access=Access to a protected member of a class +INSP.protected.member.$0.access=Access to a protected member {0} of a class + # PyArgumentEqualDefaultInspection INSP.NAME.argument.equal.default=Argument passed to function is equal to default parameter value INSP.argument.equals.to.default=Argument equals to default parameter value diff --git a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java new file mode 100644 index 000000000000..f8cbe3778b22 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java @@ -0,0 +1,65 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyReferenceExpression; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + * + * Inspection to detect situations, where + * protected member (i.e. class member with a name beginning with an underscore) + * is access outside the class or a descendant of the class where it's defined. + */ +public class PyProtectedMemberInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.protected.member.access"); + } + + @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 visitPyReferenceExpression(PyReferenceExpression node) { + final PyExpression qualifier = node.getQualifier(); + if (qualifier == null) return; + final String name = node.getName(); + if (name != null && name.startsWith("_")) { + final PyClass parentClass = PsiTreeUtil.getParentOfType(node, PyClass.class); + if (parentClass != null) { + final PsiReference reference = node.getReference(); + final PsiElement resolvedExpression = reference.resolve(); + final PyClass resolvedClass = PsiTreeUtil.getParentOfType(resolvedExpression, PyClass.class); + if (parentClass.isSubclass(resolvedClass)) + return; + } + registerProblem(node, PyBundle.message("INSP.protected.member.$0.access", name)); + } + } + + } +} diff --git a/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py b/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py new file mode 100644 index 000000000000..cef8d0097304 --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py @@ -0,0 +1,15 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + +class B(A): + def __init__(self): + A.__init__(self) + self.b = self._a + + diff --git a/python/testData/inspections/PyProtectedMemberInspection/truePositive.py b/python/testData/inspections/PyProtectedMemberInspection/truePositive.py new file mode 100644 index 000000000000..979cfd9a5ead --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/truePositive.py @@ -0,0 +1,11 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + + +print A()._a \ No newline at end of file diff --git a/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py b/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py new file mode 100644 index 000000000000..45474e7b4f58 --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py @@ -0,0 +1,12 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + +class B: + def __init__(self): + A()._a diff --git a/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java new file mode 100644 index 000000000000..5398d14fb120 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java @@ -0,0 +1,27 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyProtectedMemberInspectionTest extends PyTestCase { + + public void testTruePositive() { + doTest(); + } + + public void testTruePositiveInClass() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyProtectedMemberInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyProtectedMemberInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} From 51ca2375f2da252e20693e96bea9d938d94b73ad Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 21 Mar 2013 17:12:12 +0400 Subject: [PATCH 03/10] added quick fix for the PyAttributeOutsideInitInspection --- .../python/psi/PyElementGenerator.java | 1 + .../com/jetbrains/python/PyBundle.properties | 7 +- .../PyAttributeOutsideInitInspection.java | 4 +- .../PyMoveAttributeToInitQuickFix.java | 98 +++++++++++++++++++ .../psi/impl/PyElementGeneratorImpl.java | 11 +++ .../addPass.py | 9 ++ .../addPass_after.py | 10 ++ .../createInit.py | 7 ++ .../createInit_after.py | 9 ++ .../moveToInit.py | 10 ++ .../moveToInit_after.py | 10 ++ .../removePass.py | 9 ++ .../removePass_after.py | 9 ++ .../skipDocstring.py | 10 ++ .../skipDocstring_after.py | 13 +++ .../PyMoveAttributeToInitQuickFixTest.java | 57 +++++++++++ 16 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass_after.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit_after.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit_after.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass_after.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring.py create mode 100644 python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring_after.py create mode 100644 python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java index 8304e3e691aa..b2647c45c3e3 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java @@ -88,4 +88,5 @@ public abstract class PyElementGenerator { public abstract PsiFile createDummyFile(LanguageLevel langLevel, String contents); public abstract PyExpressionStatement createDocstring(String content); + public abstract PyPassStatement createPassStatement(); } diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 47cf202a2c2a..0bb551716d23 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -102,6 +102,9 @@ QFIX.remove.decorator=Remove decorator #PyRenameUnresolvedRefQuickFix QFIX.rename.unresolved.reference=Rename reference +#PyMoveAttributeToInitQuickFix +QFIX.move.attribute=Move attribute to __init__ method + # Intentions: INTN INTN.Family.convert.import.unqualify=Convert 'import module' to 'from module import' INTN.Family.convert.import.qualify=Convert 'from module import' to 'import module' @@ -432,8 +435,8 @@ INSP.NAME.augment.assignment=Assignment can be replaced with augmented assignmen INSP.NAME.chained.comparisons=Chained comparisons can be simplified # PyAttributeOutsideInitInspection -INSP.NAME.attribute.outside.init=Instance attribute defined outside _init_ -INSP.attribute.$0.outside.init=Instance attribute {0} defined outside _init_ +INSP.NAME.attribute.outside.init=Instance attribute defined outside __init__ +INSP.attribute.$0.outside.init=Instance attribute {0} defined outside __init__ # PyProtectedMemberInspection INSP.NAME.protected.member.access=Access to a protected member of a class diff --git a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java index 641c244ed72a..a98be42b63de 100644 --- a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java @@ -4,6 +4,7 @@ import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.inspections.quickfix.PyMoveAttributeToInitQuickFix; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyTargetExpression; @@ -58,7 +59,8 @@ public class PyAttributeOutsideInitInspection extends PyInspection { for (Map.Entry attribute : attributes.entrySet()) { if (!attributesInInit.containsKey(attribute.getKey())) { - registerProblem(attribute.getValue(), PyBundle.message("INSP.attribute.$0.outside.init", attribute.getKey())); + registerProblem(attribute.getValue(), PyBundle.message("INSP.attribute.$0.outside.init", attribute.getKey()), + new PyMoveAttributeToInitQuickFix()); } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java new file mode 100644 index 000000000000..556d77bbc090 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java @@ -0,0 +1,98 @@ +package com.jetbrains.python.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * User: ktisha + */ +public class PyMoveAttributeToInitQuickFix implements LocalQuickFix { + + public PyMoveAttributeToInitQuickFix() { + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.move.attribute"); + } + + @NonNls + @NotNull + public String getFamilyName() { + return getName(); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (!(element instanceof PyTargetExpression)) return; + final PyTargetExpression targetExpression = (PyTargetExpression)element; + + final PyClass containingClass = targetExpression.getContainingClass(); + final PyAssignmentStatement assignment = PsiTreeUtil.getParentOfType(element, PyAssignmentStatement.class); + if (containingClass == null || assignment == null) return; + + final PsiElement copy = assignment.copy(); + if (!addDefinition(copy, containingClass)) return; + + removeDefinition(assignment); + } + + private static boolean addDefinition(PsiElement copy, PyClass containingClass) { + PyFunction init = containingClass.findInitOrNew(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); + } + 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]); + return true; + } + + private static boolean removeDefinition(PyAssignmentStatement assignment) { + final PyStatementList statementList = PsiTreeUtil.getParentOfType(assignment, PyStatementList.class); + if (statementList == null) return false; + + if (statementList.getStatements().length == 1) { + final PyPassStatement passStatement = PyElementGenerator.getInstance(assignment.getProject()).createPassStatement(); + statementList.addBefore(passStatement, assignment); + } + assignment.delete(); + return true; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index d7249521648b..29c121fe571a 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -304,6 +304,17 @@ public class PyElementGeneratorImpl extends PyElementGenerator { } } + @Override + public PyPassStatement createPassStatement() { + final PyStatementList statementList = createPassStatementList(); + return (PyPassStatement)statementList.getStatements()[0]; + } + + private PyStatementList createPassStatementList() { + final PyFunction function = createFromText(LanguageLevel.getDefault(), PyFunction.class, "def foo():\n\tpass"); + return function.getStatementList(); + } + public PyExpressionStatement createDocstring(String content) { return createFromText(LanguageLevel.getDefault(), PyExpressionStatement.class, content + "\n"); diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass.py new file mode 100644 index 000000000000..2f8f8b71940a --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b = 1 diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass_after.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass_after.py new file mode 100644 index 000000000000..da65a030ea4d --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/addPass_after.py @@ -0,0 +1,10 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self._a = 1 + self.b = 1 + + def foo(self): + pass diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit.py new file mode 100644 index 000000000000..a748e483d84c --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit.py @@ -0,0 +1,7 @@ +__author__ = 'ktisha' + + +class A: + def foo(self): + self.b = 1 + c = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit_after.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit_after.py new file mode 100644 index 000000000000..f6e6e3f22f53 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/createInit_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self.b = 1 + + def foo(self): + c = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit.py new file mode 100644 index 000000000000..767bd48bb19b --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit.py @@ -0,0 +1,10 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b = 1 + c = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit_after.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit_after.py new file mode 100644 index 000000000000..2dc80743d949 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/moveToInit_after.py @@ -0,0 +1,10 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self._a = 1 + self.b = 1 + + def foo(self): + c = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass.py new file mode 100644 index 000000000000..6b9381ffa4b7 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + pass + + def foo(self): + self.b = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass_after.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass_after.py new file mode 100644 index 000000000000..b8da02ff7b17 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/removePass_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + + +class A: + def __init__(self): + self.b = 1 + + def foo(self): + pass diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring.py new file mode 100644 index 000000000000..a8ba90afa2a9 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring.py @@ -0,0 +1,10 @@ +__author__ = 'ktisha' + + +class A: + """ + class docstring + """ + + def foo(self): + self.b = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring_after.py b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring_after.py new file mode 100644 index 000000000000..d188b5eb7a24 --- /dev/null +++ b/python/testData/quickFixes/PyMoveAttributeToInitQuickFixTest/skipDocstring_after.py @@ -0,0 +1,13 @@ +__author__ = 'ktisha' + + +class A: + """ + class docstring + """ + + def __init__(self): + self.b = 1 + + def foo(self): + pass diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java new file mode 100644 index 000000000000..6279f25093b1 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java @@ -0,0 +1,57 @@ +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 void testMoveToInit() { + doInspectionTest(PyAttributeOutsideInitInspection.class, true); + } + + public void testCreateInit() { + doInspectionTest(PyAttributeOutsideInitInspection.class, true); + } + + public void testAddPass() { + doInspectionTest(PyAttributeOutsideInitInspection.class, true); + } + + public void testRemovePass() { + doInspectionTest(PyAttributeOutsideInitInspection.class, true); + } + + 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); + } + } + +} From 5fac99786ec1b69ed00e7c5b1d95707349991174 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 21 Mar 2013 23:54:06 +0100 Subject: [PATCH 04/10] automatically clear templateTesting flag --- .../jetbrains/python/refactoring/PyIntroduceTestCase.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceTestCase.java b/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceTestCase.java index 6922ec5915e5..c7b20ac1b025 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceTestCase.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceTestCase.java @@ -1,11 +1,9 @@ package com.jetbrains.python.refactoring; -import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.testFramework.LightPlatformTestCase; import com.intellij.util.Consumer; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.PyExpression; @@ -63,9 +61,8 @@ public abstract class PyIntroduceTestCase extends PyTestCase { String name = getTestName(true); myFixture.configureByFile(name + ".py"); final boolean enabled = myFixture.getEditor().getSettings().isVariableInplaceRenameEnabled(); - TemplateManagerImpl templateManager = (TemplateManagerImpl)TemplateManager.getInstance(LightPlatformTestCase.getProject()); try { - templateManager.setTemplateTesting(true); + TemplateManagerImpl.setTemplateTesting(myFixture.getProject(), getTestRootDisposable()); myFixture.getEditor().getSettings().setVariableInplaceRenameEnabled(true); IntroduceHandler handler = createHandler(); @@ -83,7 +80,6 @@ public abstract class PyIntroduceTestCase extends PyTestCase { } finally { myFixture.getEditor().getSettings().setVariableInplaceRenameEnabled(enabled); - templateManager.setTemplateTesting(false); } } } From e985159f9e69ee5e432f7a487da71db2668ec5bd Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 11:59:47 +0400 Subject: [PATCH 05/10] fixed test data --- .../PyAttributeOutsideInitInspection/truePositive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py b/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py index dba1dea38214..87fc96cfef11 100644 --- a/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py +++ b/python/testData/inspections/PyAttributeOutsideInitInspection/truePositive.py @@ -5,4 +5,4 @@ class A: self.a = 1 def foo(self): - self.b= 1 + self.b= 1 From e6d9c74ab2b823fca136e79ae50c6d8b71834704 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 12:16:27 +0400 Subject: [PATCH 06/10] used proper way to find __init__ method --- .../inspections/PyAttributeOutsideInitInspection.java | 7 ++++--- .../quickfix/PyMoveAttributeToInitQuickFix.java | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java index a98be42b63de..68002fc95d72 100644 --- a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java @@ -4,6 +4,7 @@ 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.PyMoveAttributeToInitQuickFix; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; @@ -50,9 +51,9 @@ public class PyAttributeOutsideInitInspection extends PyInspection { if (containingClass == null) return; Map attributesInInit = new HashMap(); - final PyFunction initOrNew = containingClass.findInitOrNew(true); - if (initOrNew != null) - PyClassImpl.collectInstanceAttributes(initOrNew, attributesInInit); + final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, true); + if (initMethod != null) + PyClassImpl.collectInstanceAttributes(initMethod, attributesInInit); Map attributes = new HashMap(); PyClassImpl.collectInstanceAttributes(node, attributes); diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java index 556d77bbc090..3f43328fbf0a 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java @@ -6,6 +6,7 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -45,7 +46,7 @@ public class PyMoveAttributeToInitQuickFix implements LocalQuickFix { } private static boolean addDefinition(PsiElement copy, PyClass containingClass) { - PyFunction init = containingClass.findInitOrNew(false); + PyFunction init = containingClass.findMethodByName(PyNames.INIT, true); if (init == null) { final PyStatementList classStatementList = containingClass.getStatementList(); From eafde147711720d327d3be370af2a6b113a02f62 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 14:13:18 +0400 Subject: [PATCH 07/10] 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 --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 5 ++ .../PyAttributeOutsideInitInspection.java | 2 +- .../PyClassHasNoInitInspection.java | 57 +++++++++++++++++++ .../PyUnresolvedReferencesInspection.java | 4 +- .../quickfix/AddMethodQuickFix.java | 34 +++++------ .../PyMoveAttributeToInitQuickFix.java | 29 ++-------- .../src/com/jetbrains/python/psi/PyUtil.java | 25 ++++++++ .../PyClassHasNoInitInspection/class.py | 4 ++ .../PyClassHasNoInitInspection/parentClass.py | 9 +++ .../trueNegative.py | 7 +++ .../AddMethodQuickFixTest/addInit.py | 4 ++ .../AddMethodQuickFixTest/addInit_after.py | 7 +++ .../addMethodFromInstance.py} | 0 .../addMethodFromInstance_after.py} | 0 .../addMethodFromMethod.py} | 0 .../addMethodFromMethod_after.py} | 0 .../com/jetbrains/python/PyQuickFixTest.java | 10 ---- .../PyClassHasNoInitInspectionTest.java | 27 +++++++++ .../quickFixes/AddMethodQuickFixTest.java | 24 ++++++++ .../PyMoveAttributeToInitQuickFixTest.java | 36 ++---------- .../python/quickFixes/PyQuickFixTestCase.java | 28 +++++++++ 22 files changed, 228 insertions(+), 85 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyClassHasNoInitInspection.java create mode 100644 python/testData/inspections/PyClassHasNoInitInspection/class.py create mode 100644 python/testData/inspections/PyClassHasNoInitInspection/parentClass.py create mode 100644 python/testData/inspections/PyClassHasNoInitInspection/trueNegative.py create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addInit.py create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addInit_after.py rename python/testData/{inspections/AddMethodFromInstance.py => quickFixes/AddMethodQuickFixTest/addMethodFromInstance.py} (100%) rename python/testData/{inspections/AddMethodFromInstance_after.py => quickFixes/AddMethodQuickFixTest/addMethodFromInstance_after.py} (100%) rename python/testData/{inspections/AddMethodFromMethod.py => quickFixes/AddMethodQuickFixTest/addMethodFromMethod.py} (100%) rename python/testData/{inspections/AddMethodFromMethod_after.py => quickFixes/AddMethodQuickFixTest/addMethodFromMethod_after.py} (100%) create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyClassHasNoInitInspectionTest.java create mode 100644 python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java create mode 100644 python/testSrc/com/jetbrains/python/quickFixes/PyQuickFixTestCase.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 7f2cbd10ee6d..f74baec7520e 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -317,6 +317,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 0bb551716d23..faf4d15127af 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -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 diff --git a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java index 68002fc95d72..b6add93f047c 100644 --- a/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAttributeOutsideInitInspection.java @@ -51,7 +51,7 @@ public class PyAttributeOutsideInitInspection extends PyInspection { if (containingClass == null) return; Map attributesInInit = new HashMap(); - final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, true); + final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, false); if (initMethod != null) PyClassImpl.collectInstanceAttributes(initMethod, attributesInInit); diff --git a/python/src/com/jetbrains/python/inspections/PyClassHasNoInitInspection.java b/python/src/com/jetbrains/python/inspections/PyClassHasNoInitInspection.java new file mode 100644 index 000000000000..89555fc3ce09 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyClassHasNoInitInspection.java @@ -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)); + } + } + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index e9e2b2a42e3c..da29e94bd1f3 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -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")); diff --git a/python/src/com/jetbrains/python/inspections/quickfix/AddMethodQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/AddMethodQuickFix.java index c855bd32c94a..0b4e34859e66 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/AddMethodQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/AddMethodQuickFix.java @@ -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(); } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java index 3f43328fbf0a..c18e697b7e09 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMoveAttributeToInitQuickFix.java @@ -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; } diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index db4c4677f819..9b72ceb55c05 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -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; + } } diff --git a/python/testData/inspections/PyClassHasNoInitInspection/class.py b/python/testData/inspections/PyClassHasNoInitInspection/class.py new file mode 100644 index 000000000000..d3cebf62b1c1 --- /dev/null +++ b/python/testData/inspections/PyClassHasNoInitInspection/class.py @@ -0,0 +1,4 @@ +__author__ = 'ktisha' +class A: + def foo(self): + self.b = 1 \ No newline at end of file diff --git a/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py b/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py new file mode 100644 index 000000000000..88e0fa2058d3 --- /dev/null +++ b/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' +class A: + def foo(self): + self.b = 1 + +class B(A): + def __init__(self): + self.b = 2 + diff --git a/python/testData/inspections/PyClassHasNoInitInspection/trueNegative.py b/python/testData/inspections/PyClassHasNoInitInspection/trueNegative.py new file mode 100644 index 000000000000..7b8eacb4df4c --- /dev/null +++ b/python/testData/inspections/PyClassHasNoInitInspection/trueNegative.py @@ -0,0 +1,7 @@ +__author__ = 'ktisha' +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addInit.py b/python/testData/quickFixes/AddMethodQuickFixTest/addInit.py new file mode 100644 index 000000000000..e218d09b8165 --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addInit.py @@ -0,0 +1,4 @@ +__author__ = 'ktisha' +class A(object): + def foo(self): + self.b = 1 diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addInit_after.py b/python/testData/quickFixes/AddMethodQuickFixTest/addInit_after.py new file mode 100644 index 000000000000..a4c47a805bbd --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addInit_after.py @@ -0,0 +1,7 @@ +__author__ = 'ktisha' +class A(object): + def __init__(self): + pass + + def foo(self): + self.b = 1 diff --git a/python/testData/inspections/AddMethodFromInstance.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromInstance.py similarity index 100% rename from python/testData/inspections/AddMethodFromInstance.py rename to python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromInstance.py diff --git a/python/testData/inspections/AddMethodFromInstance_after.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromInstance_after.py similarity index 100% rename from python/testData/inspections/AddMethodFromInstance_after.py rename to python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromInstance_after.py diff --git a/python/testData/inspections/AddMethodFromMethod.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromMethod.py similarity index 100% rename from python/testData/inspections/AddMethodFromMethod.py rename to python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromMethod.py diff --git a/python/testData/inspections/AddMethodFromMethod_after.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromMethod_after.py similarity index 100% rename from python/testData/inspections/AddMethodFromMethod_after.py rename to python/testData/quickFixes/AddMethodQuickFixTest/addMethodFromMethod_after.py diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index a4a236419ada..70eeef9216ca 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -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); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyClassHasNoInitInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyClassHasNoInitInspectionTest.java new file mode 100644 index 000000000000..266c13ee8d98 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyClassHasNoInitInspectionTest.java @@ -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); + } +} diff --git a/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java new file mode 100644 index 000000000000..ee4ac7e02d07 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java @@ -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")); + } + +} diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java index 6279f25093b1..b02d5fd4f092 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyMoveAttributeToInitQuickFixTest.java @@ -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")); } } diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyQuickFixTestCase.java b/python/testSrc/com/jetbrains/python/quickFixes/PyQuickFixTestCase.java new file mode 100644 index 000000000000..660dcdf13959 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyQuickFixTestCase.java @@ -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); + } +} From d74c7f2ab135c1255485a01c004d265fcabd617b Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 14:43:01 +0400 Subject: [PATCH 08/10] implemented pylint E1111 Inspection is used when an assignment is done on a function call but the inferred function doesn't return anything. --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 3 + .../PyNoneFunctionAssignmentInspection.java | 57 +++++++++++++++++++ .../noReturn.py | 6 ++ .../noType.py | 6 ++ .../pass.py | 6 ++ .../returnNone.py | 6 ++ .../trueNegative.py | 6 ++ ...yNoneFunctionAssignmentInspectionTest.java | 35 ++++++++++++ 9 files changed, 126 insertions(+) create mode 100644 python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/noType.py create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py create mode 100644 python/testData/inspections/PyNoneFunctionAssignmentInspection/trueNegative.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index f74baec7520e..bb7888c51277 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -318,6 +318,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index faf4d15127af..a888fe16383c 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -476,6 +476,9 @@ 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 +#PyNoneFunctionAssignmentInspection +INSP.NAME.none.function.assignment=Assigning function call that doesn't return anything (None) +INSP.none.function.assignment=Function {0} doesn''t return anything # Refactoring # introduce diff --git a/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java new file mode 100644 index 000000000000..a2840609827c --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspection.java @@ -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.psi.PyAssignmentStatement; +import com.jetbrains.python.psi.PyCallExpression; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.types.PyNoneType; +import com.jetbrains.python.psi.types.PyType; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + * + * pylint E1111 + * + * Used when an assignment is done on a function call but the inferred function doesn't return anything. + */ +public class PyNoneFunctionAssignmentInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.none.function.assignment"); + } + + @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 visitPyAssignmentStatement(PyAssignmentStatement node) { + final PyExpression value = node.getAssignedValue(); + if (value instanceof PyCallExpression) { + final PyType type = myTypeEvalContext.getType(value); + final PyExpression callee = ((PyCallExpression)value).getCallee(); + if (type instanceof PyNoneType && callee != null) { + registerProblem(node, PyBundle.message("INSP.none.function.assignment", callee.getName())); + } + } + } + } +} diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py new file mode 100644 index 000000000000..fd8857107147 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +def foo(): + a = 1 + +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/noType.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noType.py new file mode 100644 index 000000000000..1f540cd70028 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noType.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +def foo(x): + return x + +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py new file mode 100644 index 000000000000..3f099ddc98f1 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +def foo(): + pass + +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py new file mode 100644 index 000000000000..c1d5ad50a263 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +def foo(): + return None + +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/trueNegative.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/trueNegative.py new file mode 100644 index 000000000000..af29087226f1 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/trueNegative.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +def foo(): + return 3 + +b = foo() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java new file mode 100644 index 000000000000..7247cc07afe6 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyNoneFunctionAssignmentInspectionTest.java @@ -0,0 +1,35 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyNoneFunctionAssignmentInspectionTest extends PyTestCase { + + public void testPass() { + doTest(); + } + + public void testReturnNone() { + doTest(); + } + + public void testNoReturn() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + public void testNoType() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyNoneFunctionAssignmentInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyNoneFunctionAssignmentInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} From d7281a83a963b0776e42f7d2b2129e0af6fb9cfc Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 15:29:35 +0400 Subject: [PATCH 09/10] implemented pylint W0601 Inspection is used when a variable is defined through the "global" statement but the variable is not defined in the module scope. --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 8 ++- .../PyGlobalUndefinedInspection.java | 51 +++++++++++++++++++ .../PyClassHasNoInitInspection/parentClass.py | 2 +- .../trueNegative.py | 8 +++ .../truePositive.py | 6 +++ .../noReturn.py | 2 +- .../pass.py | 2 +- .../returnNone.py | 2 +- .../PyGlobalUndefinedInspectionTest.java | 23 +++++++++ 10 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyGlobalUndefinedInspection.java create mode 100644 python/testData/inspections/PyGlobalUndefinedInspection/trueNegative.py create mode 100644 python/testData/inspections/PyGlobalUndefinedInspection/truePositive.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyGlobalUndefinedInspectionTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index bb7888c51277..2eb8d1ce0151 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -319,6 +319,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index a888fe16383c..c224e40e0673 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -474,11 +474,15 @@ 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 +INSP.parent.$0.has.no.init=Parent ''{0}'' has no __init__ method #PyNoneFunctionAssignmentInspection INSP.NAME.none.function.assignment=Assigning function call that doesn't return anything (None) -INSP.none.function.assignment=Function {0} doesn''t return anything +INSP.none.function.assignment=Function ''{0}'' doesn''t return anything + +#PyGlobalUndefinedInspection +INSP.NAME.global.undefined=Global variable is undefined at the module level +INSP.NAME.global.$0.undefined=Global variable ''{0}'' is undefined at the module level # Refactoring # introduce diff --git a/python/src/com/jetbrains/python/inspections/PyGlobalUndefinedInspection.java b/python/src/com/jetbrains/python/inspections/PyGlobalUndefinedInspection.java new file mode 100644 index 000000000000..132c55ddf7a7 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyGlobalUndefinedInspection.java @@ -0,0 +1,51 @@ +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.psi.PyGlobalStatement; +import com.jetbrains.python.psi.PyTargetExpression; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + * + * pylint W0601 + */ +public class PyGlobalUndefinedInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.global.undefined"); + } + + @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 visitPyGlobalStatement(PyGlobalStatement node) { + final PyTargetExpression[] globals = node.getGlobals(); + + for (PyTargetExpression global : globals) { + if (global.getReference().resolve() == global) { + registerProblem(global, PyBundle.message("INSP.NAME.global.$0.undefined", global.getName())); + } + } + } + } +} diff --git a/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py b/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py index 88e0fa2058d3..b28f8965302b 100644 --- a/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py +++ b/python/testData/inspections/PyClassHasNoInitInspection/parentClass.py @@ -3,7 +3,7 @@ class A: def foo(self): self.b = 1 -class B(A): +class B(A): def __init__(self): self.b = 2 diff --git a/python/testData/inspections/PyGlobalUndefinedInspection/trueNegative.py b/python/testData/inspections/PyGlobalUndefinedInspection/trueNegative.py new file mode 100644 index 000000000000..928d6c65d41b --- /dev/null +++ b/python/testData/inspections/PyGlobalUndefinedInspection/trueNegative.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' +bar = 1 + +def foo(): + global bar + print bar + +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyGlobalUndefinedInspection/truePositive.py b/python/testData/inspections/PyGlobalUndefinedInspection/truePositive.py new file mode 100644 index 000000000000..d34a29765a7d --- /dev/null +++ b/python/testData/inspections/PyGlobalUndefinedInspection/truePositive.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' +def foo(): + global bar + print bar + +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py index fd8857107147..7b14161bb1fc 100644 --- a/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/noReturn.py @@ -3,4 +3,4 @@ __author__ = 'ktisha' def foo(): a = 1 -b = foo() \ No newline at end of file +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py index 3f099ddc98f1..6caff7dada73 100644 --- a/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/pass.py @@ -3,4 +3,4 @@ __author__ = 'ktisha' def foo(): pass -b = foo() \ No newline at end of file +b = foo() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py index c1d5ad50a263..c92aadde89b7 100644 --- a/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/returnNone.py @@ -3,4 +3,4 @@ __author__ = 'ktisha' def foo(): return None -b = foo() \ No newline at end of file +b = foo() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyGlobalUndefinedInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyGlobalUndefinedInspectionTest.java new file mode 100644 index 000000000000..cad5cd00286b --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyGlobalUndefinedInspectionTest.java @@ -0,0 +1,23 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyGlobalUndefinedInspectionTest extends PyTestCase { + + public void testTruePositive() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyGlobalUndefinedInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyGlobalUndefinedInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} From 00eafcb0118f93c49d81df3805e38b73c5004257 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Fri, 22 Mar 2013 15:38:12 +0400 Subject: [PATCH 10/10] added tests for PyUtil.addElementToStatementList --- .../AddMethodQuickFixTest/addInitAfterDocstring.py | 5 +++++ .../AddMethodQuickFixTest/addInitAfterDocstring_after.py | 9 +++++++++ .../AddMethodQuickFixTest/addMethodReplacePass.py | 5 +++++ .../AddMethodQuickFixTest/addMethodReplacePass_after.py | 6 ++++++ .../python/quickFixes/AddMethodQuickFixTest.java | 8 ++++++++ 5 files changed, 33 insertions(+) create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring.py create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring_after.py create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass.py create mode 100644 python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass_after.py diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring.py b/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring.py new file mode 100644 index 000000000000..fa1256c76f0f --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring.py @@ -0,0 +1,5 @@ +__author__ = 'ktisha' +class A(object): + """ class docstring""" + def foo(self): + self.b = 1 diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring_after.py b/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring_after.py new file mode 100644 index 000000000000..035e6aabdfd1 --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addInitAfterDocstring_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' +class A(object): + """ class docstring""" + + def __init__(self): + pass + + def foo(self): + self.b = 1 diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass.py new file mode 100644 index 000000000000..7e93b2109fca --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass.py @@ -0,0 +1,5 @@ +class A: + pass + +a = A() +a.y() diff --git a/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass_after.py b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass_after.py new file mode 100644 index 000000000000..3082267d4d02 --- /dev/null +++ b/python/testData/quickFixes/AddMethodQuickFixTest/addMethodReplacePass_after.py @@ -0,0 +1,6 @@ +class A: + def y(self): + pass + +a = A() +a.y() diff --git a/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java index ee4ac7e02d07..01d97615b242 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java @@ -13,6 +13,14 @@ public class AddMethodQuickFixTest extends PyQuickFixTestCase { doInspectionTest(PyClassHasNoInitInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "__init__", "A")); } + public void testAddInitAfterDocstring() { + doInspectionTest(PyClassHasNoInitInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "__init__", "A")); + } + + public void testAddMethodReplacePass() { + doInspectionTest(PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A")); + } + public void testAddMethodFromInstance() { doInspectionTest(PyUnresolvedReferencesInspection.class, PyBundle.message("QFIX.NAME.add.method.$0.to.class.$1", "y", "A")); }