diff --git a/python/resources/inspectionDescriptions/PyProtectedMemberInspection.html b/python/resources/inspectionDescriptions/PyProtectedMemberInspection.html
index d84dd24d812e..fd2dd25071e5 100644
--- a/python/resources/inspectionDescriptions/PyProtectedMemberInspection.html
+++ b/python/resources/inspectionDescriptions/PyProtectedMemberInspection.html
@@ -1,5 +1,5 @@
-This inspection warns if a protected member is access outside the class or a descendant of the class where it's defined.
+This inspection warns if a protected member is accessed outside the class, a descendant of the class where it's defined or a module.
\ No newline at end of file
diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml
index 2b48df70978a..8b17bf728103 100644
--- a/python/src/META-INF/python-core-common.xml
+++ b/python/src/META-INF/python-core-common.xml
@@ -403,7 +403,7 @@
-
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index d01d3c94af16..c2dade447f31 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -541,7 +541,6 @@ 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
INSP.protected.member.$0.access.module=Access to a protected member {0} of a module
diff --git a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java
index 59b1c4bb22c6..92f19004fa42 100644
--- a/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyProtectedMemberInspection.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,18 +33,17 @@ import com.jetbrains.python.inspections.quickfix.PyAddPropertyForFieldQuickFix;
import com.jetbrains.python.inspections.quickfix.PyMakePublicQuickFix;
import com.jetbrains.python.inspections.quickfix.PyRenameElementQuickFix;
import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyModuleType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.testing.PyTestsSharedKt;
-import org.jetbrains.annotations.Nls;
+import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
/**
* User: ktisha
@@ -57,13 +56,6 @@ public class PyProtectedMemberInspection extends PyInspection {
public boolean ignoreTestFunctions = true;
public boolean ignoreAnnotations = false;
- @Nls
- @NotNull
- @Override
- public String getDisplayName() {
- return PyBundle.message("INSP.NAME.protected.member.access");
- }
-
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
@@ -179,6 +171,47 @@ public class PyProtectedMemberInspection extends PyInspection {
}
return null;
}
+
+ @Override
+ public void visitPyFromImportStatement(PyFromImportStatement node) {
+ final PyReferenceExpression source = node.getImportSource();
+ if (source == null) return;
+
+ final Set dunderAlls = collectDunderAlls(source);
+ if (dunderAlls == null) return;
+
+ StreamEx
+ .of(node.getImportElements())
+ .map(PyImportElement::getImportReferenceExpression)
+ .nonNull()
+ .filter(referenceExpression -> !dunderAlls.contains(referenceExpression.getName()))
+ .forEach(
+ referenceExpression -> {
+ final String message = "'" + referenceExpression.getName() + "' is not declared in __all__";
+ registerProblem(referenceExpression, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
+ }
+ );
+ }
+
+ @Nullable
+ private Set collectDunderAlls(@NotNull PyReferenceExpression source) {
+ final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
+
+ final List> resolvedDunderAlls = StreamEx
+ .of(source.getReference(resolveContext).multiResolve(false))
+ .map(ResolveResult::getElement)
+ .select(PyFile.class)
+ .map(PyFile::getDunderAll)
+ .toList();
+ if (resolvedDunderAlls.isEmpty()) return null;
+
+ final Set result = new HashSet<>();
+ for (List dunderAll : resolvedDunderAlls) {
+ if (dunderAll == null) return null;
+ result.addAll(dunderAll);
+ }
+ return result;
+ }
}
@Nullable
diff --git a/python/testData/inspections/PyProtectedMemberInspection/DunderAll/a.py b/python/testData/inspections/PyProtectedMemberInspection/DunderAll/a.py
new file mode 100644
index 000000000000..8a808471e477
--- /dev/null
+++ b/python/testData/inspections/PyProtectedMemberInspection/DunderAll/a.py
@@ -0,0 +1,8 @@
+from m1 import m1m1
+print(m1m1)
+
+from m1 import m1m2
+print(m1m2)
+
+from m1 import m1m1, m1m2
+print(m1m1)
\ No newline at end of file
diff --git a/python/testData/inspections/PyProtectedMemberInspection/DunderAll/m1.py b/python/testData/inspections/PyProtectedMemberInspection/DunderAll/m1.py
new file mode 100644
index 000000000000..e622f48ce2be
--- /dev/null
+++ b/python/testData/inspections/PyProtectedMemberInspection/DunderAll/m1.py
@@ -0,0 +1,9 @@
+__all__ = ["m1m1"]
+
+
+def m1m1():
+ pass
+
+
+def m1m2():
+ pass
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java
index b92beb696623..3e7a9991fcd7 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyProtectedMemberInspectionTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,6 +80,11 @@ public class PyProtectedMemberInspectionTest extends PyInspectionTestCase {
doMultiFileTest();
}
+ // PY-14056
+ public void testDunderAll() {
+ doMultiFileTest();
+ }
+
public void testClassInAnotherModule() {
doMultiFileTest();
}