PY-29894 Fix Python quick fixes for PSI consistency

This commit is contained in:
Elizaveta Shashkova
2018-07-17 14:58:31 +03:00
parent c39d6f79ce
commit 717484f15a
19 changed files with 91 additions and 32 deletions
@@ -132,6 +132,9 @@ public class PyConvertTripleQuotedStringIntention extends PyBaseIntentionAction
parent instanceof PyArgumentList && ArrayUtil.getFirstElement(((PyArgumentList)parent).getArguments()) == pyString)) {
expression = ((PyParenthesizedExpression)expression).getContainedExpression();
}
if (expression instanceof PyStringLiteralExpression && ((PyStringLiteralExpression)expression).isDocString()) {
expression = elementGenerator.createStringLiteralAlreadyEscaped(result.toString());
}
if (expression != null) {
pyString.replace(expression);
}
@@ -19,6 +19,7 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.util.ObjectUtils;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.PyBinaryExpression;
@@ -28,6 +29,8 @@ import com.jetbrains.python.psi.PyExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import static com.intellij.util.ObjectUtils.assertNotNull;
import static com.jetbrains.python.psi.PyUtil.as;
@@ -103,10 +106,60 @@ public class ChainedComparisonsQuickFix implements LocalQuickFix {
}
}
final PyBinaryExpression binaryExpression = elementGenerator.createBinaryExpression(operator, newLeftExpression, newRightExpression);
leftExpression.replace(binaryExpression);
leftExpression.replace(makePsiConsistentBinaryExpression(project, binaryExpression));
rightExpression.delete();
}
@NotNull
private static PyExpression makePsiConsistentBinaryExpression(@NotNull Project project, @NotNull PyBinaryExpression binaryExpression) {
final ArrayList<PyExpression> elements = new ArrayList<>();
final ArrayList<String> operators = new ArrayList<>();
collectExpressionsDfs(elements, operators, binaryExpression);
PyExpression resultExpression = buildResultExpression(project, elements, operators);
return ObjectUtils.chooseNotNull(resultExpression, binaryExpression);
}
private static void collectExpressionsDfs(@NotNull ArrayList<PyExpression> elements,
@NotNull ArrayList<String> operators,
@NotNull PyBinaryExpression expression) {
final PyExpression rightExpr = expression.getRightExpression();
if (rightExpr instanceof PyBinaryExpression && isComparisonExpression(rightExpr)) {
collectExpressionsDfs(elements, operators, (PyBinaryExpression)rightExpr);
}
else {
elements.add(rightExpr);
}
if (expression.getPsiOperator() != null) {
operators.add(expression.getPsiOperator().getText());
}
final PyExpression leftExpr = expression.getLeftExpression();
if (leftExpr instanceof PyBinaryExpression && isComparisonExpression(leftExpr)) {
collectExpressionsDfs(elements, operators, (PyBinaryExpression)leftExpr);
}
else {
elements.add(leftExpr);
}
}
@Nullable
private static PyExpression buildResultExpression(@NotNull Project project,
@NotNull ArrayList<PyExpression> elements,
@NotNull ArrayList<String> operators) {
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
final int size = elements.size();
if (elements.isEmpty()) return null;
PyExpression currentResult = elements.get(size - 1);
int i = size - 2;
while (i >= 0) {
PyExpression nextElement = elements.get(i);
String nextOperator = operators.get(i);
currentResult = elementGenerator.createBinaryExpression(nextOperator, currentResult, nextElement);
i--;
}
return currentResult;
}
@NotNull
private static PsiElement getLeftestOperator(@NotNull PyBinaryExpression expression) {
PsiElement op = expression.getPsiOperator();
@@ -48,7 +48,10 @@ public class ConvertSetLiteralQuickFix implements LocalQuickFix {
stringBuilder.append(expressions[i].getText());
}
PyStatement newElement = elementGenerator.createFromText(LanguageLevel.getDefault(), PyExpressionStatement.class, "set([" + stringBuilder.toString() + "])");
setLiteral.replace(newElement);
final PsiElement parent = setLiteral.getParent();
if (parent instanceof PyExpressionStatement) {
parent.replace(newElement);
}
}
}
}
@@ -53,7 +53,8 @@ public class PyAddExceptionSuperClassQuickFix implements LocalQuickFix {
assert expressionList != null;
final ASTNode nameNode = ((PyClass)psiElement).getNameNode();
assert nameNode != null;
psiElement.addAfter(expressionList, nameNode.getPsi());
final PsiElement oldArgList = nameNode.getPsi().getNextSibling();
oldArgList.replace(expressionList);
}
}
}
@@ -50,7 +50,10 @@ public class PyConvertToNewStyleQuickFix implements LocalQuickFix {
assert list != null;
final ASTNode node = pyClass.getNameNode();
assert node != null;
pyClass.addAfter(list, node.getPsi());
final PsiElement oldArgList = node.getPsi().getNextSibling();
if (oldArgList instanceof PyArgumentList) {
oldArgList.replace(list);
}
}
}
}
@@ -20,8 +20,7 @@ import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
public class PyRemoveAssignmentQuickFix implements LocalQuickFix {
@@ -37,7 +36,10 @@ public class PyRemoveAssignmentQuickFix implements LocalQuickFix {
if (assignment instanceof PyAssignmentStatement) {
final PyExpression value = ((PyAssignmentStatement)assignment).getAssignedValue();
if (value != null) {
assignment.replace(value);
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
final PyExpressionStatement newStatement =
elementGenerator.createFromText(LanguageLevel.forElement(assignment), PyExpressionStatement.class, value.getText());
assignment.replace(newStatement);
}
}
}
@@ -20,9 +20,7 @@ import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyArgumentList;
import com.jetbrains.python.psi.PyCallExpression;
import com.jetbrains.python.psi.PyDecorator;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
public class PyRemoveCallQuickFix implements LocalQuickFix {
@@ -43,6 +41,10 @@ public class PyRemoveCallQuickFix implements LocalQuickFix {
final PyArgumentList argumentList = ((PyCallExpression)call).getArgumentList();
assert argumentList != null;
argumentList.delete();
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
// regenerate element for Psi consistency, because it isn't PyCallExpression anymore
final PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(call), call.getText());
call.replace(expression);
}
}
}
@@ -17,6 +17,7 @@ package com.jetbrains.python.inspections.quickfix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
@@ -60,7 +61,15 @@ public class RedundantParenthesesQuickFix implements LocalQuickFix {
else if (element instanceof PyArgumentList) {
LOG.assertTrue(element.getParent() instanceof PyClass, "Parent type: " + element.getParent().getClass());
LOG.assertTrue(((PyArgumentList)element).getArguments().length == 0, "Argument list: " + element.getText());
element.delete();
final ASTNode nameNode = PyElementGenerator.getInstance(project).createFromText(
LanguageLevel.forElement(element), PyClass.class, "class A: pass").getNameNode();
if (nameNode != null) {
final PsiElement emptyArgList = nameNode.getPsi().getNextSibling();
element.replace(emptyArgList);
}
else {
element.delete();
}
}
}
@@ -1 +1 @@
'x' +<caret> u'привет'
t = 'x' +<caret> u'привет'
@@ -1 +1 @@
u'xпривет'
t = u'xпривет'
@@ -1,4 +1,5 @@
def foo():
pass
a = <caret>foo()
@@ -1,4 +1,5 @@
def foo():
pass
foo()
@@ -251,7 +251,6 @@ public class PyQuickFixTest extends PyTestCase {
// PY-15506
public void testEmptyListOfBaseClasses() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doInspectionTest(PyRedundantParenthesesInspection.class, PyBundle.message("QFIX.redundant.parentheses"), true, true);
}
@@ -267,13 +266,11 @@ public class PyQuickFixTest extends PyTestCase {
// PY-3126
public void testChainedComparison1() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doInspectionTest(PyChainedComparisonsInspection.class, PyBundle.message("QFIX.chained.comparison"), true, true);
}
// PY-3126
public void testChainedComparison2() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doInspectionTest(PyChainedComparisonsInspection.class, PyBundle.message("QFIX.chained.comparison"), true, true);
}
@@ -284,7 +281,6 @@ public class PyQuickFixTest extends PyTestCase {
// PY-5623
public void testChainedComparison4() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doInspectionTest(PyChainedComparisonsInspection.class, PyBundle.message("QFIX.chained.comparison"), true, true);
}
@@ -295,7 +291,6 @@ public class PyQuickFixTest extends PyTestCase {
// PY-20004
public void testChainedComparison7() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doInspectionTest(PyChainedComparisonsInspection.class, PyBundle.message("QFIX.chained.comparison"), true, true);
}
@@ -70,7 +70,6 @@ public class PyIntentionTest extends PyTestCase {
}
public void testConvertSetLiteral() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doTest(PyBundle.message("INTN.convert.set.literal.to"), LanguageLevel.PYTHON26);
}
@@ -275,7 +274,6 @@ public class PyIntentionTest extends PyTestCase {
}
public void testConvertTripleQuotedUnicodeString() { //PY-7152
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doTest(PyBundle.message("INTN.triple.quoted.string"));
}
@@ -299,7 +297,6 @@ public class PyIntentionTest extends PyTestCase {
}
public void testConvertTripleQuotedEmptyString() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doTest(PyBundle.message("INTN.triple.quoted.string"), LanguageLevel.PYTHON34);
}
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.intentions;
import com.intellij.testFramework.PsiTestUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.LanguageLevel;
@@ -53,7 +52,6 @@ public class PyStringConcatenationToFormatIntentionTest extends PyIntentionTestC
}
public void testUnicodeSecondString() { //PY-7463
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doTest(PyBundle.message("INTN.replace.plus.with.format.operator"), LanguageLevel.PYTHON26);
}
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.quickFixes;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyQuickFixTestCase;
@@ -25,7 +24,6 @@ import com.jetbrains.python.inspections.PyExceptionInheritInspection;
public class PyAddExceptionSuperClassQuickFixTest extends PyQuickFixTestCase {
public void testEmptySuperList() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doQuickFixTest(PyExceptionInheritInspection.class, PyBundle.message("QFIX.NAME.add.exception.base"));
}
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.quickFixes;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyQuickFixTestCase;
@@ -25,7 +24,6 @@ import com.jetbrains.python.inspections.PyOldStyleClassesInspection;
public class PyConvertToNewStyleQuickFixTest extends PyQuickFixTestCase {
public void testEmptySuperClassList() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doQuickFixTest(PyOldStyleClassesInspection.class, PyBundle.message("QFIX.convert.to.new.style"));
}
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.quickFixes;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyQuickFixTestCase;
@@ -25,7 +24,6 @@ import com.jetbrains.python.inspections.PyNoneFunctionAssignmentInspection;
public class PyRemoveAssignmentQuickFixTest extends PyQuickFixTestCase {
public void testEmptyFunction() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doQuickFixTest(PyNoneFunctionAssignmentInspection.class, PyBundle.message("QFIX.NAME.remove.assignment"));
}
@@ -15,7 +15,6 @@
*/
package com.jetbrains.python.quickFixes;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyQuickFixTestCase;
@@ -25,7 +24,6 @@ import com.jetbrains.python.inspections.PyCallingNonCallableInspection;
public class PyRemoveCallQuickFixTest extends PyQuickFixTestCase {
public void testExpression() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doQuickFixTest(PyCallingNonCallableInspection.class, PyBundle.message("QFIX.NAME.remove.call"));
}
@@ -34,7 +32,6 @@ public class PyRemoveCallQuickFixTest extends PyQuickFixTestCase {
}
public void testClass() {
PsiTestUtil.disablePsiTextConsistencyChecks(getTestRootDisposable());
doQuickFixTest(PyCallingNonCallableInspection.class, PyBundle.message("QFIX.NAME.remove.call"));
}