From 1b3528f31bcda39409a3a37a11de7ec96ee4d523 Mon Sep 17 00:00:00 2001 From: "alexey.ivanov" Date: Fri, 26 Mar 2010 16:29:33 +0300 Subject: [PATCH] Py-698 Inspection to check that comparisons with None are performed with 'is' rather than '==' --- .../com/jetbrains/python/PyBundle.properties | 5 ++ .../actions/ComparisonWithNoneQuickFix.java | 47 ++++++++++++ .../PyComparisonWithNoneInspection.java | 74 +++++++++++++++++++ .../PythonInspectionToolProvider.java | 3 +- .../inspections/ComparisonWithNone.py | 2 + .../inspections/ComparisonWithNone_after.py | 2 + .../expected.xml | 18 +++++ .../src/test.py | 5 ++ .../com/jetbrains/python/PyQuickFixTest.java | 5 ++ .../python/PythonInspectionsTest.java | 5 ++ 10 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 python/src/com/jetbrains/python/actions/ComparisonWithNoneQuickFix.java create mode 100644 python/src/com/jetbrains/python/inspections/PyComparisonWithNoneInspection.java create mode 100644 python/testData/inspections/ComparisonWithNone.py create mode 100644 python/testData/inspections/ComparisonWithNone_after.py create mode 100644 python/testData/inspections/PyComparisonWithNoneInspection/expected.xml create mode 100644 python/testData/inspections/PyComparisonWithNoneInspection/src/test.py diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index f0a618f21219..fc4a17608cd6 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -42,6 +42,8 @@ QFIX.simplify=Simplify QFIX.move.from.future.import=Move 'from __future__ import' to a correct place +QFIX.replace.equality=Replace equality + # 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' @@ -210,6 +212,9 @@ INSP.expression.can.be.simplified=Expression can be simplified # PyFromFutureImportInspection INSP.NAME.from.future.import=from __future__ import must be the first executable statement +# PyComparisonWithNoneInspection +INSP.NAME.comparison.with.none=Comparison with None performed with equality operators + # Refactoring # introduce refactoring.introduce.name.error=Incorrect name diff --git a/python/src/com/jetbrains/python/actions/ComparisonWithNoneQuickFix.java b/python/src/com/jetbrains/python/actions/ComparisonWithNoneQuickFix.java new file mode 100644 index 000000000000..2205a3f43540 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ComparisonWithNoneQuickFix.java @@ -0,0 +1,47 @@ +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.jetbrains.python.PyBundle; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyBinaryExpression; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyElementType; +import com.jetbrains.python.psi.PyExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * Author: Alexey.Ivanov + * Date: 24.03.2010 + * Time: 22:00:49 + */ +public class ComparisonWithNoneQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.equality"); + } + + @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 instanceof PyBinaryExpression) { + PyBinaryExpression binaryExpression = (PyBinaryExpression)problemElement; + PyElementType operator = binaryExpression.getOperator(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + String temp; + temp = (operator == PyTokenTypes.EQEQ) ? "is" : "is not"; + PyExpression expression = elementGenerator.createBinaryExpression(project, temp, + binaryExpression.getLeftExpression(), + binaryExpression.getRightExpression()); + binaryExpression.replace(expression); + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyComparisonWithNoneInspection.java b/python/src/com/jetbrains/python/inspections/PyComparisonWithNoneInspection.java new file mode 100644 index 000000000000..cf6951cafe62 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyComparisonWithNoneInspection.java @@ -0,0 +1,74 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElementVisitor; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.actions.ComparisonWithNoneQuickFix; +import com.jetbrains.python.psi.PyBinaryExpression; +import com.jetbrains.python.psi.PyElementType; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyReferenceExpression; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * Author: Alexey.Ivanov + * Date: 24.03.2010 + * Time: 21:33:51 + */ +public class PyComparisonWithNoneInspection extends LocalInspectionTool { + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return PyBundle.message("INSP.GROUP.python"); + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.comparison.with.none"); + } + + @NotNull + @Override + public String getShortName() { + return "PyComparisonWithNoneInspection"; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends PyInspectionVisitor { + + public Visitor(final ProblemsHolder holder) { + super(holder); + } + + @Override + public void visitPyBinaryExpression(PyBinaryExpression node) { + final PyExpression rightExpression = node.getRightExpression(); + if (rightExpression instanceof PyReferenceExpression) { + if (PyNames.NONE.equals(rightExpression.getName())) { + final PyElementType operator = node.getOperator(); + if (operator == PyTokenTypes.EQEQ || operator == PyTokenTypes.NE || operator == PyTokenTypes.NE_OLD) { + registerProblem(node, "Comparison with None performed with equality operators", new ComparisonWithNoneQuickFix()); + } + } + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java index 93e894a753c6..722a9880587f 100644 --- a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java +++ b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java @@ -33,7 +33,8 @@ public class PythonInspectionToolProvider implements InspectionToolProvider { PyUnboundLocalVariableInspection.class, PyStatementEffectInspection.class, PySimplifyBooleanCheckInspection.class, - PyFromFutureImportInspection.class + PyFromFutureImportInspection.class, + PyComparisonWithNoneInspection.class }; } } diff --git a/python/testData/inspections/ComparisonWithNone.py b/python/testData/inspections/ComparisonWithNone.py new file mode 100644 index 000000000000..7cc83d4cd37f --- /dev/null +++ b/python/testData/inspections/ComparisonWithNone.py @@ -0,0 +1,2 @@ +if a != None: + pass \ No newline at end of file diff --git a/python/testData/inspections/ComparisonWithNone_after.py b/python/testData/inspections/ComparisonWithNone_after.py new file mode 100644 index 000000000000..45bf98440d77 --- /dev/null +++ b/python/testData/inspections/ComparisonWithNone_after.py @@ -0,0 +1,2 @@ +if a is not None: + pass \ No newline at end of file diff --git a/python/testData/inspections/PyComparisonWithNoneInspection/expected.xml b/python/testData/inspections/PyComparisonWithNoneInspection/expected.xml new file mode 100644 index 000000000000..325fd43beb3e --- /dev/null +++ b/python/testData/inspections/PyComparisonWithNoneInspection/expected.xml @@ -0,0 +1,18 @@ + + + + test.py + 1 + Comparison with None performed with equality operators + + + test.py + 1 + Comparison with None performed with equality operators + + + test.py + 1 + Comparison with None performed with equality operators + + \ No newline at end of file diff --git a/python/testData/inspections/PyComparisonWithNoneInspection/src/test.py b/python/testData/inspections/PyComparisonWithNoneInspection/src/test.py new file mode 100644 index 000000000000..ab4489b94cef --- /dev/null +++ b/python/testData/inspections/PyComparisonWithNoneInspection/src/test.py @@ -0,0 +1,5 @@ +if a <> None and b != None and c == None: + pass + +if a is None and b is not None: + pass \ 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 2a950b5f7699..de1c9c2d6c8f 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -115,6 +115,11 @@ public class PyQuickFixTest extends PyLightFixtureTestCase { PyBundle.message("QFIX.move.from.future.import"), true, true); } + public void testComparisonWithNoneQuickFix() throws Exception { + doInspectionTest("ComparisonWithNone.py", PyComparisonWithNoneInspection.class, + PyBundle.message("QFIX.replace.equality"), true, true); + } + @NonNls protected String getTestDataPath() { return PythonTestUtil.getTestDataPath() + "/inspections/"; diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 310ed8497a06..35dcba397187 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -155,4 +155,9 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { LocalInspectionTool inspection = new PyFromFutureImportInspection(); doTest(getTestName(false), inspection); } + + public void testPyComparisonWithNoneInspection() throws Throwable { + LocalInspectionTool inspection = new PyComparisonWithNoneInspection(); + doTest(getTestName(false), inspection); + } }