added quick fix for the PyAttributeOutsideInitInspection

This commit is contained in:
Ekaterina Tuzova
2013-03-21 17:12:12 +04:00
parent d95b343465
commit 51ca2375f2
16 changed files with 271 additions and 3 deletions
@@ -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();
}
@@ -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
@@ -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<String, PyTargetExpression> 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());
}
}
}
@@ -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;
}
}
@@ -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");
@@ -0,0 +1,9 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self._a = 1
def foo(self):
self.<caret>b = 1
@@ -0,0 +1,10 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self._a = 1
self.b = 1
def foo(self):
pass
@@ -0,0 +1,7 @@
__author__ = 'ktisha'
class A:
def foo(self):
self.<caret>b = 1
c = 1
@@ -0,0 +1,9 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self.b = 1
def foo(self):
c = 1
@@ -0,0 +1,10 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self._a = 1
def foo(self):
self.<caret>b = 1
c = 1
@@ -0,0 +1,10 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self._a = 1
self.b = 1
def foo(self):
c = 1
@@ -0,0 +1,9 @@
__author__ = 'ktisha'
class A:
def __init__(self):
pass
def foo(self):
self.<caret>b = 1
@@ -0,0 +1,9 @@
__author__ = 'ktisha'
class A:
def __init__(self):
self.b = 1
def foo(self):
pass
@@ -0,0 +1,10 @@
__author__ = 'ktisha'
class A:
"""
class docstring
"""
def foo(self):
self.<caret>b = 1
@@ -0,0 +1,13 @@
__author__ = 'ktisha'
class A:
"""
class docstring
"""
def __init__(self):
self.b = 1
def foo(self):
pass
@@ -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);
}
}
}