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);
+ }
+}