From bae885d945dc39eece9f04c4e81cf04776d3868e Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Wed, 2 Mar 2011 14:44:54 +0300 Subject: [PATCH] added PY-2952 "Unnecessary backslash" inspection --- .../com/jetbrains/python/PyBundle.properties | 5 ++ .../RemoveUnnecessaryBackslashQuickFix.java | 62 +++++++++++++ .../PyUnnecessaryBackslashInspection.java | 89 +++++++++++++++++++ .../PythonInspectionToolProvider.java | 1 + .../PyUnnecessaryBackslashInspection/test.py | 47 ++++++++++ .../inspections/UnnecessaryBackslash.py | 4 + .../UnnecessaryBackslashInArguments.py | 4 + .../UnnecessaryBackslashInArguments_after.py | 4 + .../inspections/UnnecessaryBackslash_after.py | 4 + .../com/jetbrains/python/PyQuickFixTest.java | 20 +++++ .../python/PythonInspectionsTest.java | 3 + 11 files changed, 243 insertions(+) create mode 100644 python/src/com/jetbrains/python/actions/RemoveUnnecessaryBackslashQuickFix.java create mode 100644 python/src/com/jetbrains/python/inspections/PyUnnecessaryBackslashInspection.java create mode 100644 python/testData/inspections/PyUnnecessaryBackslashInspection/test.py create mode 100644 python/testData/inspections/UnnecessaryBackslash.py create mode 100644 python/testData/inspections/UnnecessaryBackslashInArguments.py create mode 100644 python/testData/inspections/UnnecessaryBackslashInArguments_after.py create mode 100644 python/testData/inspections/UnnecessaryBackslash_after.py diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index a4243de057b5..d2485e2b2226 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -66,6 +66,9 @@ QFIX.introduce.variable=Introduce variable for statement QFIX.unresolved.reference.add.future=Add 'from __future__ import with_statement'' +# RemoveUnnecessaryBackslashQuickFix +QFIX.remove.unnecessary.backslash=Remove unnecessary backslash in expression + # 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' @@ -348,6 +351,8 @@ INSP.NAME.oldstyle.class=Old-style class contains new-style class features # PyCompatibilityInspection INSP.NAME.compatibility=Code compatibility inspection +# PyUnnecessaryBackslashInspection +INSP.NAME.unnecessary.backslash=Unnecessary backslash # Refactoring # introduce diff --git a/python/src/com/jetbrains/python/actions/RemoveUnnecessaryBackslashQuickFix.java b/python/src/com/jetbrains/python/actions/RemoveUnnecessaryBackslashQuickFix.java new file mode 100644 index 000000000000..7c524824318c --- /dev/null +++ b/python/src/com/jetbrains/python/actions/RemoveUnnecessaryBackslashQuickFix.java @@ -0,0 +1,62 @@ +package com.jetbrains.python.actions; + +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.PsiWhiteSpace; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.Stack; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +/** + * User: catherine + * + * QuickFix to remove all unnecessary backslashes in expression + */ +public class RemoveUnnecessaryBackslashQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.remove.unnecessary.backslash"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement problemElement = descriptor.getPsiElement(); + if (problemElement != null) { + PyElement parent = PsiTreeUtil.getParentOfType(problemElement, PySequenceExpression.class, PyDictLiteralExpression.class, + PyParenthesizedExpression.class, PyArgumentList.class, PyParameterList.class); + removeBackSlash(parent); + } + } + + private static void removeBackSlash(PyElement parent) { + if (parent != null) { + Stack stack = new Stack(); + if (parent instanceof PyParenthesizedExpression) + stack.push(((PyParenthesizedExpression)parent).getContainedExpression()); + else + stack.push(parent); + while (!stack.isEmpty()) { + PsiElement el = stack.pop(); + PsiWhiteSpace[] children = PsiTreeUtil.getChildrenOfType(el, PsiWhiteSpace.class); + if (children != null) { + for (PsiWhiteSpace ws : children) { + if (ws.getText().contains("\\")) { + ws.delete(); + } + } + } + for (PsiElement psiElement : el.getChildren()) { + stack.push(psiElement); + } + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnnecessaryBackslashInspection.java b/python/src/com/jetbrains/python/inspections/PyUnnecessaryBackslashInspection.java new file mode 100644 index 000000000000..71e7c2abf254 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyUnnecessaryBackslashInspection.java @@ -0,0 +1,89 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.Stack; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.actions.RemoveUnnecessaryBackslashQuickFix; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * User: catherine + * + * Inspection to highlight backslashes in places where line continuation is implicit (inside (), [], {}). + */ +public class PyUnnecessaryBackslashInspection extends PyInspection { + + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.unnecessary.backslash"); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends PyInspectionVisitor { + public Visitor(ProblemsHolder holder) { + super(holder); + } + + @Override + public void visitPyParameterList(final PyParameterList list) { + findProblem(list); + } + + @Override + public void visitPyArgumentList(final PyArgumentList list) { + findProblem(list); + } + + @Override + public void visitPyParenthesizedExpression(final PyParenthesizedExpression expression) { + Stack stack = new Stack(); + stack.push(expression.getContainedExpression()); + while (!stack.isEmpty()) { + PsiElement element = stack.pop(); + findProblem(element); + for (PsiElement psiElement : element.getChildren()) { + stack.push(psiElement); + } + } + } + + @Override + public void visitPyDictLiteralExpression(final PyDictLiteralExpression expression) { + findProblem(expression); + } + + @Override + public void visitPyListLiteralExpression(final PyListLiteralExpression expression) { + findProblem(expression); + } + + @Override + public void visitPySetLiteralExpression(final PySetLiteralExpression expression) { + findProblem(expression); + } + + private void findProblem (final PsiElement expression) { + PsiWhiteSpace[] children = PsiTreeUtil.getChildrenOfType(expression, PsiWhiteSpace.class); + if (children != null) { + for (PsiWhiteSpace ws : children) { + if (ws.getText().contains("\\")) { + registerProblem(ws, "Unnecessary backslash in expression.", new RemoveUnnecessaryBackslashQuickFix()); + } + } + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java index c5c5e696db53..ac783d929b70 100644 --- a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java +++ b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java @@ -54,6 +54,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider { PyOldStyleClassesInspection.class, PyCompatibilityInspection.class, PyListCreationInspection.class, + PyUnnecessaryBackslashInspection.class, }; } } diff --git a/python/testData/inspections/PyUnnecessaryBackslashInspection/test.py b/python/testData/inspections/PyUnnecessaryBackslashInspection/test.py new file mode 100644 index 000000000000..fc1560357c03 --- /dev/null +++ b/python/testData/inspections/PyUnnecessaryBackslashInspection/test.py @@ -0,0 +1,47 @@ + +def foo(year, month, day, hour, minute, second): + if 1900 < year < 2100 and 1 <= month <= 12 \ + and 1 <= day <= 31 and 0 <= hour < 24 \ + and 0 <= minute < 60 and 0 <= second < 60: # Looks like a valid date + return 1 + + +if (True \ + or True \ + or False): + print("false") + +var1 = (1,1,\ + 2,\ + 3, + 4) + +var2 = [1,2,\ + 3,\ + 4] + +var3 = {1, 2,\ + 3,4} + +var4 = {1:1, 2:2,\ + 3:3, + 4:4} + + +assert (val>4, \ + "val is too small") + +var5 = (val1 < 20) and \ + (val2 < 30) and \ + (val3 < 40) + +var6 = ('1' + '2' + '3' + \ + '4' + '5') + + +def foo(a, b, \ + c): + pass + +foo(1, 2, \ + 3) \ No newline at end of file diff --git a/python/testData/inspections/UnnecessaryBackslash.py b/python/testData/inspections/UnnecessaryBackslash.py new file mode 100644 index 000000000000..dbec3aaf879b --- /dev/null +++ b/python/testData/inspections/UnnecessaryBackslash.py @@ -0,0 +1,4 @@ +var1 = (1,1,\ + 2,\ + 3, + 4) \ No newline at end of file diff --git a/python/testData/inspections/UnnecessaryBackslashInArguments.py b/python/testData/inspections/UnnecessaryBackslashInArguments.py new file mode 100644 index 000000000000..b98798d3889e --- /dev/null +++ b/python/testData/inspections/UnnecessaryBackslashInArguments.py @@ -0,0 +1,4 @@ +def foo(a, b,\ + c,\ + d): + pass \ No newline at end of file diff --git a/python/testData/inspections/UnnecessaryBackslashInArguments_after.py b/python/testData/inspections/UnnecessaryBackslashInArguments_after.py new file mode 100644 index 000000000000..a0d238d8490f --- /dev/null +++ b/python/testData/inspections/UnnecessaryBackslashInArguments_after.py @@ -0,0 +1,4 @@ +def foo(a, b, + c, + d): + pass \ No newline at end of file diff --git a/python/testData/inspections/UnnecessaryBackslash_after.py b/python/testData/inspections/UnnecessaryBackslash_after.py new file mode 100644 index 000000000000..d2d0e6013a6f --- /dev/null +++ b/python/testData/inspections/UnnecessaryBackslash_after.py @@ -0,0 +1,4 @@ +var1 = (1,1, + 2, + 3, + 4) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index de07aac11da2..2f1a567e31eb 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -220,6 +220,26 @@ public class PyQuickFixTest extends PyLightFixtureTestCase { PyBundle.message("QFIX.list.creation"), true, true); } + public void testUnnecessaryBackslash() { + String[] testFiles = new String[]{"UnnecessaryBackslash.py"}; + myFixture.enableInspections(PyUnnecessaryBackslashInspection.class); + myFixture.configureByFiles(testFiles); + myFixture.checkHighlighting(true, false, true); + IntentionAction intentionAction = myFixture.getAvailableIntention(PyBundle.message("QFIX.remove.unnecessary.backslash")); + myFixture.launchAction(intentionAction); + myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after")); + } + + public void testUnnecessaryBackslashInArgumentList() { + String[] testFiles = new String[]{"UnnecessaryBackslashInArguments.py"}; + myFixture.enableInspections(PyUnnecessaryBackslashInspection.class); + myFixture.configureByFiles(testFiles); + myFixture.checkHighlighting(true, false, true); + IntentionAction intentionAction = myFixture.getAvailableIntention(PyBundle.message("QFIX.remove.unnecessary.backslash")); + myFixture.launchAction(intentionAction); + myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after")); + } + @Override @NonNls protected String getTestDataPath() { diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 0a805262fde0..40db21308f17 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -304,4 +304,7 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { doHighlightingTest(PyStringFormatInspection.class); } + public void testPyUnnecessaryBackslashInspection() { //PY-2952 + doHighlightingTest(PyUnnecessaryBackslashInspection.class); + } }