From 272c03d776bbb1c672acd131bdb06cf598044151 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 24 Aug 2017 20:01:01 +0300 Subject: [PATCH] Raise weak warning about non-public API usage defined by __all__ PY-14056 Fixed. --- .../PyProtectedMemberInspection.html | 2 +- python/src/META-INF/python-core-common.xml | 2 +- .../com/jetbrains/python/PyBundle.properties | 1 - .../PyProtectedMemberInspection.java | 57 +++++++++++++++---- .../DunderAll/a.py | 8 +++ .../DunderAll/m1.py | 9 +++ .../PyProtectedMemberInspectionTest.java | 7 ++- 7 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 python/testData/inspections/PyProtectedMemberInspection/DunderAll/a.py create mode 100644 python/testData/inspections/PyProtectedMemberInspection/DunderAll/m1.py 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(); }