diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 2eb8d1ce0151..3d1de32997db 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -321,6 +321,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 8fc2359087fd..def59ebbf6d8 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -105,6 +105,12 @@ QFIX.rename.unresolved.reference=Rename reference #PyMoveAttributeToInitQuickFix QFIX.move.attribute=Move attribute to __init__ method +#PyMakeMethodStaticQuickFix +QFIX.NAME.make.static=Make method static + +#PyMakeFunctionFromMethodQuickFix +QFIX.NAME.make.function=Make function from 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' @@ -472,6 +478,10 @@ INSP.NAME.decorator.outside.class=Class specific decorator on method outside cla # PyPackageRequirementsInspection INSP.NAME.requirements=Package requirements +# PyMethodMayBeStaticInspection +INSP.NAME.method.may.be.static=Method may be static +INSP.method.may.be.static=Method #ref may be 'static' + # PyClassHasNoInitInspection INSP.NAME.class.has.no.init=Class has no __init__ method INSP.class.has.no.init=Class has no __init__ method diff --git a/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java new file mode 100644 index 000000000000..4482e44e4f75 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyMethodMayBeStaticInspection.java @@ -0,0 +1,75 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.inspections.quickfix.PyMakeFunctionFromMethodQuickFix; +import com.jetbrains.python.inspections.quickfix.PyMakeMethodStaticQuickFix; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + * + */ +public class PyMethodMayBeStaticInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.method.may.be.static"); + } + + @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) { + if (PyNames.getBuiltinMethods(LanguageLevel.forElement(node)).containsKey(node.getName())) return; + final PyClass containingClass = node.getContainingClass(); + if (containingClass == null) return; + + final PyStatementList statementList = node.getStatementList(); + if (statementList == null) return; + + final PyStatement[] statements = statementList.getStatements(); + + if (statements.length == 1 && statements[0] instanceof PyPassStatement) return; + + final boolean[] mayBeStatic = {true}; + PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() { + @Override + public void visitPyReferenceExpression(PyReferenceExpression node) { + if (PyNames.CANONICAL_SELF.equals(node.getName())) { + mayBeStatic[0] = false; + } + } + + }; + node.accept(visitor); + final PsiElement identifier = node.getNameIdentifier(); + if (mayBeStatic[0] && identifier != null) { + registerProblem(identifier, PyBundle.message("INSP.method.may.be.static"), ProblemHighlightType.WEAK_WARNING, + null, new PyMakeMethodStaticQuickFix(), new PyMakeFunctionFromMethodQuickFix()); + } + } + + } +} diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java new file mode 100644 index 000000000000..aeafb78780a7 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java @@ -0,0 +1,47 @@ +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.PsiFile; +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 PyMakeFunctionFromMethodQuickFix implements LocalQuickFix { + public PyMakeFunctionFromMethodQuickFix() { + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.NAME.make.function"); + } + + @NonNls + @NotNull + public String getFamilyName() { + return getName(); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (problemFunction == null) return; + final PyClass containingClass = problemFunction.getContainingClass(); + if (containingClass == null) return; + + if (!PyUtil.deleteParameter(problemFunction, 0)) return; + + final PsiElement copy = problemFunction.copy(); + final PyStatementList classStatementList = containingClass.getStatementList(); + classStatementList.deleteChildRange(problemFunction, problemFunction); + final PsiFile file = containingClass.getContainingFile(); + file.addAfter(copy, containingClass); + } +} diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMakeMethodStaticQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeMethodStaticQuickFix.java new file mode 100644 index 000000000000..51ecbf3d8cb3 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeMethodStaticQuickFix.java @@ -0,0 +1,49 @@ +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 PyMakeMethodStaticQuickFix implements LocalQuickFix { + public PyMakeMethodStaticQuickFix() { + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.NAME.make.static"); + } + + @NonNls + @NotNull + public String getFamilyName() { + return getName(); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (problemFunction == null) return; + if (!PyUtil.deleteParameter(problemFunction, 0)) return; + + PyElementGenerator generator = PyElementGenerator.getInstance(project); + final PyFunction function = + generator.createFromText(LanguageLevel.forElement(problemFunction), PyFunction.class, "@staticmethod\ndef foo():\n\tpass"); + final PyDecoratorList decoratorList = function.getDecoratorList(); + assert decoratorList != null; + final PyDecorator[] decorators = decoratorList.getDecorators(); + final PyDecorator decorator = decorators[0]; + problemFunction.addBefore(decorator, problemFunction.getFirstChild()); + + + + } +} diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 429a3d4f9aab..6db77aa84a54 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -642,6 +642,19 @@ public class PyUtil { return AccessDirection.READ; } + public static boolean deleteParameter(@NotNull final PyFunction problemFunction, int index) { + final PyParameterList parameterList = problemFunction.getParameterList(); + final PyParameter[] parameters = parameterList.getParameters(); + if (parameters.length <= 0) return false; + + PsiElement first = parameters[index]; + PsiElement last = parameters.length > index + 1 ? parameters[index + 1] : parameterList.getLastChild(); + PsiElement prevSibling = last.getPrevSibling() != null ? last.getPrevSibling() : parameters[index]; + + parameterList.deleteChildRange(first, prevSibling); + return true; + } + public static class KnownDecoratorProviderHolder { public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME); diff --git a/python/testData/inspections/PyMethodMayBeStaticInspection/empty.py b/python/testData/inspections/PyMethodMayBeStaticInspection/empty.py new file mode 100644 index 000000000000..f86a8af8a751 --- /dev/null +++ b/python/testData/inspections/PyMethodMayBeStaticInspection/empty.py @@ -0,0 +1,5 @@ +__author__ = 'ktisha' + +class Child(Base): + def f(self): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyMethodMayBeStaticInspection/init.py b/python/testData/inspections/PyMethodMayBeStaticInspection/init.py new file mode 100644 index 000000000000..3ca7623b9f9e --- /dev/null +++ b/python/testData/inspections/PyMethodMayBeStaticInspection/init.py @@ -0,0 +1,5 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + test = 1 diff --git a/python/testData/inspections/PyMethodMayBeStaticInspection/trueNegative.py b/python/testData/inspections/PyMethodMayBeStaticInspection/trueNegative.py new file mode 100644 index 000000000000..2820496c274d --- /dev/null +++ b/python/testData/inspections/PyMethodMayBeStaticInspection/trueNegative.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +class Child(Base): + + def f(self): + self.test = 1 \ No newline at end of file diff --git a/python/testData/inspections/PyMethodMayBeStaticInspection/truePositive.py b/python/testData/inspections/PyMethodMayBeStaticInspection/truePositive.py new file mode 100644 index 000000000000..dfc0bc6bbfa5 --- /dev/null +++ b/python/testData/inspections/PyMethodMayBeStaticInspection/truePositive.py @@ -0,0 +1,6 @@ +__author__ = 'ktisha' + +class Child(Base): + + def f(self): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam.py new file mode 100644 index 000000000000..62608d2ad5fd --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self, ): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam_after.py new file mode 100644 index 000000000000..fd34c29fae0c --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/emptyParam_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + +def f(): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod.py new file mode 100644 index 000000000000..29ebf6111fb8 --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def f(self, x): + test = 1 + + def __init__(self): + super(Child, self).__init__() + diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py new file mode 100644 index 000000000000..a83c41befff6 --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py @@ -0,0 +1,10 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + +def f(x): + test = 1 + diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam.py new file mode 100644 index 000000000000..b1358761a08e --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam_after.py new file mode 100644 index 000000000000..fd34c29fae0c --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/oneParam_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + +def f(): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams.py new file mode 100644 index 000000000000..6be57fd831c3 --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self, x): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams_after.py new file mode 100644 index 000000000000..dbc949cbb80b --- /dev/null +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/twoParams_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + +def f(x): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam.py new file mode 100644 index 000000000000..62608d2ad5fd --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self, ): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam_after.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam_after.py new file mode 100644 index 000000000000..b2fe125ab34f --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/emptyParam_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + @staticmethod + def f(): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam.py new file mode 100644 index 000000000000..b1358761a08e --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam_after.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam_after.py new file mode 100644 index 000000000000..b2fe125ab34f --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/oneParam_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + @staticmethod + def f(): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams.py new file mode 100644 index 000000000000..6be57fd831c3 --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams.py @@ -0,0 +1,8 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + def f(self, x): + test = 1 \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams_after.py b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams_after.py new file mode 100644 index 000000000000..744a333f8fe7 --- /dev/null +++ b/python/testData/quickFixes/PyMakeMethodStaticQuickFixTest/twoParams_after.py @@ -0,0 +1,9 @@ +__author__ = 'ktisha' + +class Child(Base): + def __init__(self): + super(Child, self).__init__() + + @staticmethod + def f(x): + test = 1 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java new file mode 100644 index 000000000000..4a198d7983a0 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyMethodMayBeStaticInspectionTest.java @@ -0,0 +1,31 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyMethodMayBeStaticInspectionTest extends PyTestCase { + + public void testTruePositive() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + public void testEmpty() { + doTest(); + } + + public void testInit() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyMethodMayBeStaticInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyMethodMayBeStaticInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyMakeFunctionFromMethodQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyMakeFunctionFromMethodQuickFixTest.java new file mode 100644 index 000000000000..cf95fd298914 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyMakeFunctionFromMethodQuickFixTest.java @@ -0,0 +1,27 @@ +package com.jetbrains.python.quickFixes; + +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.inspections.PyMethodMayBeStaticInspection; + +/** + * User: ktisha + */ +public class PyMakeFunctionFromMethodQuickFixTest extends PyQuickFixTestCase { + + public void testOneParam() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function")); + } + + public void testTwoParams() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function")); + } + + public void testEmptyParam() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function")); + } + + public void testFirstMethod() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.function")); + } + +} diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyMakeMethodStaticQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyMakeMethodStaticQuickFixTest.java new file mode 100644 index 000000000000..a13152fac0c3 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyMakeMethodStaticQuickFixTest.java @@ -0,0 +1,23 @@ +package com.jetbrains.python.quickFixes; + +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.inspections.PyMethodMayBeStaticInspection; + +/** + * User: ktisha + */ +public class PyMakeMethodStaticQuickFixTest extends PyQuickFixTestCase { + + public void testOneParam() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.static")); + } + + public void testTwoParams() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.static")); + } + + public void testEmptyParam() { + doInspectionTest(PyMethodMayBeStaticInspection.class, PyBundle.message("QFIX.NAME.make.static")); + } + +}