From 3d49b6001ed339b49c29ebce9187f246104ca6c3 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Wed, 24 Apr 2013 20:17:42 +0400 Subject: [PATCH] added convert method to property intention --- python/src/META-INF/python-plugin-common.xml | 5 + .../com/jetbrains/python/PyBundle.properties | 3 + .../PyConvertMethodToPropertyIntention.java | 98 +++++++++++++++++++ .../noReturn.py | 12 +++ .../paramList.py | 13 +++ .../property.py | 13 +++ .../simple.py | 12 +++ .../simple_after.py | 13 +++ ...yConvertMethodToPropertyIntentionTest.java | 30 ++++++ .../intentions/PyIntentionTestCase.java | 34 +++++++ 10 files changed, 233 insertions(+) create mode 100644 python/src/com/jetbrains/python/codeInsight/intentions/PyConvertMethodToPropertyIntention.java create mode 100644 python/testData/intentions/PyConvertMethodToPropertyIntentionTest/noReturn.py create mode 100644 python/testData/intentions/PyConvertMethodToPropertyIntentionTest/paramList.py create mode 100644 python/testData/intentions/PyConvertMethodToPropertyIntentionTest/property.py create mode 100644 python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple.py create mode 100644 python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple_after.py create mode 100644 python/testSrc/com/jetbrains/python/intentions/PyConvertMethodToPropertyIntentionTest.java create mode 100644 python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index b280a750bcdd..ef15f13a8316 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -137,6 +137,11 @@ + + com.jetbrains.python.codeInsight.intentions.PyConvertMethodToPropertyIntention + Python + + com.jetbrains.python.codeInsight.intentions.ImportToImportFromIntention Python diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 071f5422c125..52b3f491fe8c 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -217,6 +217,9 @@ INTN.yield.from=Transform explicit iteration with 'yield' into 'yield from' expr #PyConvertStaticMethodToFunctionIntention INTN.convert.static.method.to.function=Convert static method to function +#PyConvertMethodToPropertyIntention +INTN.convert.method.to.property=Convert method to property + # Conflict checker CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert. diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertMethodToPropertyIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertMethodToPropertyIntention.java new file mode 100644 index 000000000000..825366c1b03c --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertMethodToPropertyIntention.java @@ -0,0 +1,98 @@ +package com.jetbrains.python.codeInsight.intentions; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.refactoring.PyRefactoringUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * User: ktisha + */ +public class PyConvertMethodToPropertyIntention extends BaseIntentionAction { + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.convert.method.to.property"); + } + + @NotNull + public String getText() { + return PyBundle.message("INTN.convert.method.to.property"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!(file instanceof PyFile)) { + return false; + } + if (!LanguageLevel.forElement(file).isAtLeast(LanguageLevel.PYTHON26)) return false; + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function == null) return false; + final PyClass containingClass = function.getContainingClass(); + if (containingClass == null) return false; + if (function.getParameterList().getParameters().length > 1) return false; + + final PyDecoratorList decoratorList = function.getDecoratorList(); + if (decoratorList != null) return false; + + final boolean[] available = {false}; + function.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyReturnStatement(PyReturnStatement node) { + available[0] = true; + } + }); + + return available[0]; + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset()); + PyFunction problemFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (problemFunction == null) return; + final PyClass containingClass = problemFunction.getContainingClass(); + if (containingClass == null) return; + final List usages = PyRefactoringUtil.findUsages(problemFunction, false); + + final PyDecoratorList problemDecoratorList = problemFunction.getDecoratorList(); + List decoTexts = new ArrayList(); + decoTexts.add("@property"); + if (problemDecoratorList != null) { + final PyDecorator[] decorators = problemDecoratorList.getDecorators(); + for (PyDecorator deco : decorators) { + decoTexts.add(deco.getText()); + } + } + + PyElementGenerator generator = PyElementGenerator.getInstance(project); + final PyDecoratorList decoratorList = generator.createDecoratorList(decoTexts.toArray(new String[decoTexts.size()])); + + if (problemDecoratorList != null) { + problemDecoratorList.replace(decoratorList); + } + else { + problemFunction.addBefore(decoratorList, problemFunction.getFirstChild()); + } + + for (UsageInfo usage : usages) { + final PsiElement usageElement = usage.getElement(); + if (usageElement instanceof PyReferenceExpression) { + final PsiElement parent = usageElement.getParent(); + if (parent instanceof PyCallExpression) { + final PyArgumentList argumentList = ((PyCallExpression)parent).getArgumentList(); + if (argumentList != null) argumentList.delete(); + } + } + } + } +} diff --git a/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/noReturn.py b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/noReturn.py new file mode 100644 index 000000000000..649a90b03958 --- /dev/null +++ b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/noReturn.py @@ -0,0 +1,12 @@ +class MyClass(object): + """ + My class to show intention. + """ + def __init__(self): + self._x = None + + def x(self): + print self._x + + +x = MyClass().x() \ No newline at end of file diff --git a/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/paramList.py b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/paramList.py new file mode 100644 index 000000000000..d2426d62d4f6 --- /dev/null +++ b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/paramList.py @@ -0,0 +1,13 @@ +class MyClass(object): + """ + My class to show intention. + """ + def __init__(self): + self._x = None + + def x(self, y): + print y + return self._x + + +x = MyClass().x() \ No newline at end of file diff --git a/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/property.py b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/property.py new file mode 100644 index 000000000000..188567a6e00c --- /dev/null +++ b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/property.py @@ -0,0 +1,13 @@ +class MyClass(object): + """ + My class to show intention. + """ + def __init__(self): + self._x = None + + @property + def x(self): + return self._x + + +x = MyClass().x() \ No newline at end of file diff --git a/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple.py b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple.py new file mode 100644 index 000000000000..ec8c4daecb41 --- /dev/null +++ b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple.py @@ -0,0 +1,12 @@ +class MyClass(object): + """ + My class to show intention. + """ + def __init__(self): + self._x = None + + def x(self): + return self._x + + +x = MyClass().x() \ No newline at end of file diff --git a/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple_after.py b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple_after.py new file mode 100644 index 000000000000..1c8040301451 --- /dev/null +++ b/python/testData/intentions/PyConvertMethodToPropertyIntentionTest/simple_after.py @@ -0,0 +1,13 @@ +class MyClass(object): + """ + My class to show intention. + """ + def __init__(self): + self._x = None + + @property + def x(self): + return self._x + + +x = MyClass().x \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyConvertMethodToPropertyIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyConvertMethodToPropertyIntentionTest.java new file mode 100644 index 000000000000..f1b9c5bf2362 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/intentions/PyConvertMethodToPropertyIntentionTest.java @@ -0,0 +1,30 @@ +/* + * User: anna + * Date: 06-Mar-2008 + */ +package com.jetbrains.python.intentions; + +import com.jetbrains.python.PyBundle; + +/** + * User : ktisha + */ +public class PyConvertMethodToPropertyIntentionTest extends PyIntentionTestCase { + + public void testParamList() throws Exception { + doNegateIntentionTest(PyBundle.message("INTN.convert.method.to.property")); + } + + public void testSimple() throws Exception { + doIntentionTest(PyBundle.message("INTN.convert.method.to.property")); + } + + public void testProperty() throws Exception { + doNegateIntentionTest(PyBundle.message("INTN.convert.method.to.property")); + } + + public void testNoReturn() throws Exception { + doNegateIntentionTest(PyBundle.message("INTN.convert.method.to.property")); + } + +} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java new file mode 100644 index 000000000000..517305e2ca1c --- /dev/null +++ b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTestCase.java @@ -0,0 +1,34 @@ +package com.jetbrains.python.intentions; + +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 PyIntentionTestCase extends PyTestCase { + @Override + @NonNls + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/intentions/" + getClass().getSimpleName(); + } + + + protected void doIntentionTest(final String hint) { + final String testFileName = getTestName(true); + myFixture.configureByFile(testFileName + ".py"); + final IntentionAction intentionAction = myFixture.findSingleIntention(hint); + assertNotNull(intentionAction); + myFixture.launchAction(intentionAction); + myFixture.checkResultByFile(testFileName + "_after.py", true); + } + + protected void doNegateIntentionTest(final String hint) { + final String testFileName = getTestName(true); + myFixture.configureByFile(testFileName + ".py"); + final IntentionAction intentionAction = myFixture.getAvailableIntention(hint); + assertNull(intentionAction); + } +}