Raise weak warning about non-public API usage defined by __all__

PY-14056 Fixed.
This commit is contained in:
Semyon Proshev
2017-09-22 16:48:05 +03:00
parent dacb33d33a
commit 272c03d776
7 changed files with 70 additions and 16 deletions
@@ -1,5 +1,5 @@
<html>
<body>
This inspection warns if a protected member is access outside the class or a descendant of the class where it&#39;s defined.
This inspection warns if a protected member is accessed outside the class, a descendant of the class where it&#39;s defined or a module.
</body>
</html>
+1 -1
View File
@@ -403,7 +403,7 @@
<localInspection language="Python" shortName="PyClassHasNoInitInspection" suppressId="PyClassHasNoInit" displayName="Class has no __init__ method" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyClassHasNoInitInspection"/>
<localInspection language="Python" shortName="PyNoneFunctionAssignmentInspection" suppressId="PyNoneFunctionAssignment" displayName="Assigning function call that doesn't return anything" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyNoneFunctionAssignmentInspection"/>
<localInspection language="Python" shortName="PyGlobalUndefinedInspection" suppressId="PyGlobalUndefined" displayName="Global variable is undefined at the module level" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyGlobalUndefinedInspection"/>
<localInspection language="Python" shortName="PyProtectedMemberInspection" suppressId="PyProtectedMember" displayName="Access to a protected member of a class" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyProtectedMemberInspection"/>
<localInspection language="Python" shortName="PyProtectedMemberInspection" suppressId="PyProtectedMember" displayName="Access to a protected member of a class or a module" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyProtectedMemberInspection"/>
<localInspection language="Python" shortName="PyMethodMayBeStaticInspection" suppressId="PyMethodMayBeStatic" displayName="Method may be static" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyMethodMayBeStaticInspection"/>
<localInspection language="Python" shortName="PyDocstringTypesInspection" suppressId="PyDocstringTypes" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.docstring.types" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyDocstringTypesInspection"/>
<localInspection language="Python" shortName="PyShadowingBuiltinsInspection" suppressId="PyShadowingBuiltins" displayName="Shadowing built-ins" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyShadowingBuiltinsInspection"/>
@@ -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
@@ -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<String> 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<String> collectDunderAlls(@NotNull PyReferenceExpression source) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext);
final List<List<String>> 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<String> result = new HashSet<>();
for (List<String> dunderAll : resolvedDunderAlls) {
if (dunderAll == null) return null;
result.addAll(dunderAll);
}
return result;
}
}
@Nullable
@@ -0,0 +1,8 @@
from m1 import m1m1
print(m1m1)
from m1 import <weak_warning descr="'m1m2' is not declared in __all__">m1m2</weak_warning>
print(m1m2)
from m1 import m1m1, <weak_warning descr="'m1m2' is not declared in __all__">m1m2</weak_warning>
print(m1m1)
@@ -0,0 +1,9 @@
__all__ = ["m1m1"]
def m1m1():
pass
def m1m2():
pass
@@ -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();
}