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);
+ }
+}