From d95b3434654beee70de321b3d6bac43642569f4e Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 21 Mar 2013 15:49:23 +0400 Subject: [PATCH] implemented W0212 pylint inspection Access to a protected member %s of a client class --- python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 4 ++ .../PyProtectedMemberInspection.java | 65 +++++++++++++++++++ .../trueNegative.py | 15 +++++ .../truePositive.py | 11 ++++ .../truePositiveInClass.py | 12 ++++ .../PyProtectedMemberInspectionTest.java | 27 ++++++++ 7 files changed, 135 insertions(+) create mode 100644 python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java create mode 100644 python/testData/inspections/PyProtectedMemberInspection/trueNegative.py create mode 100644 python/testData/inspections/PyProtectedMemberInspection/truePositive.py create mode 100644 python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 429afe953c8a..7f2cbd10ee6d 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -317,6 +317,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index c4573b17bf21..47cf202a2c2a 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -435,6 +435,10 @@ INSP.NAME.chained.comparisons=Chained comparisons can be simplified 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 +INSP.protected.member.$0.access=Access to a protected member {0} of a class + # PyArgumentEqualDefaultInspection INSP.NAME.argument.equal.default=Argument passed to function is equal to default parameter value INSP.argument.equals.to.default=Argument equals to default parameter value diff --git a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java new file mode 100644 index 000000000000..f8cbe3778b22 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java @@ -0,0 +1,65 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyReferenceExpression; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: ktisha + * + * Inspection to detect situations, where + * protected member (i.e. class member with a name beginning with an underscore) + * is access outside the class or a descendant of the class where it's defined. + */ +public class PyProtectedMemberInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.protected.member.access"); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + return new Visitor(holder, session); + } + + + private static class Visitor extends PyInspectionVisitor { + public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyReferenceExpression(PyReferenceExpression node) { + final PyExpression qualifier = node.getQualifier(); + if (qualifier == null) return; + final String name = node.getName(); + if (name != null && name.startsWith("_")) { + final PyClass parentClass = PsiTreeUtil.getParentOfType(node, PyClass.class); + if (parentClass != null) { + final PsiReference reference = node.getReference(); + final PsiElement resolvedExpression = reference.resolve(); + final PyClass resolvedClass = PsiTreeUtil.getParentOfType(resolvedExpression, PyClass.class); + if (parentClass.isSubclass(resolvedClass)) + return; + } + registerProblem(node, PyBundle.message("INSP.protected.member.$0.access", name)); + } + } + + } +} diff --git a/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py b/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py new file mode 100644 index 000000000000..cef8d0097304 --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/trueNegative.py @@ -0,0 +1,15 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + +class B(A): + def __init__(self): + A.__init__(self) + self.b = self._a + + diff --git a/python/testData/inspections/PyProtectedMemberInspection/truePositive.py b/python/testData/inspections/PyProtectedMemberInspection/truePositive.py new file mode 100644 index 000000000000..979cfd9a5ead --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/truePositive.py @@ -0,0 +1,11 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + + +print A()._a \ No newline at end of file diff --git a/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py b/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py new file mode 100644 index 000000000000..45474e7b4f58 --- /dev/null +++ b/python/testData/inspections/PyProtectedMemberInspection/truePositiveInClass.py @@ -0,0 +1,12 @@ +__author__ = 'ktisha' + +class A: + def __init__(self): + self._a = 1 + + def foo(self): + self.b= 1 + +class B: + def __init__(self): + A()._a diff --git a/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java new file mode 100644 index 000000000000..5398d14fb120 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java @@ -0,0 +1,27 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyTestCase; + +/** + * User: ktisha + */ +public class PyProtectedMemberInspectionTest extends PyTestCase { + + public void testTruePositive() { + doTest(); + } + + public void testTruePositiveInClass() { + doTest(); + } + + public void testTrueNegative() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyProtectedMemberInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyProtectedMemberInspection.class); + myFixture.checkHighlighting(false, false, true); + } +}