diff --git a/python-checks-testkit/src/main/java/org/sonar/python/checks/utils/PythonCheckVerifier.java b/python-checks-testkit/src/main/java/org/sonar/python/checks/utils/PythonCheckVerifier.java index 6b3565d067..a8f9e51f00 100644 --- a/python-checks-testkit/src/main/java/org/sonar/python/checks/utils/PythonCheckVerifier.java +++ b/python-checks-testkit/src/main/java/org/sonar/python/checks/utils/PythonCheckVerifier.java @@ -75,6 +75,13 @@ public static void verify(List paths, PythonCheck check) { createVerifier(files, check, globalSymbolsPerModule, baseDirFile).assertOneOrMoreIssues(); } + public static void verifyNoIssue(List paths, PythonCheck check) { + List files = paths.stream().map(File::new).collect(Collectors.toList()); + File baseDirFile = new File(files.get(0).getParent()); + Map> globalSymbolsPerModule = TestPythonVisitorRunner.globalSymbols(files, baseDirFile); + createVerifier(files, check, globalSymbolsPerModule, baseDirFile).assertNoIssues(); + } + private static MultiFileVerifier createVerifier(List files, PythonCheck check, Map> globalSymbolsPerModule, @Nullable File baseDir) { MultiFileVerifier multiFileVerifier = MultiFileVerifier.create(files.get(0).toPath(), UTF_8); for (File file : files) { diff --git a/python-checks-testkit/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifierAdditionalTest.java b/python-checks-testkit/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifierAdditionalTest.java index ac8e61bf76..25cf968e36 100644 --- a/python-checks-testkit/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifierAdditionalTest.java +++ b/python-checks-testkit/src/test/java/org/sonar/python/checks/utils/PythonCheckVerifierAdditionalTest.java @@ -19,6 +19,7 @@ */ package org.sonar.python.checks.utils; +import java.util.Collections; import org.assertj.core.api.Assertions; import org.junit.Test; import org.sonar.plugins.python.api.PythonSubscriptionCheck; @@ -57,6 +58,7 @@ public void initialize(Context context) { } }; PythonCheckVerifier.verifyNoIssue(BASE_DIR + "no_issue.py", check); + PythonCheckVerifier.verifyNoIssue(Collections.singletonList(BASE_DIR + "no_issue.py"), check); try { PythonCheckVerifier.verifyNoIssue(BASE_DIR + "file_issue.py", check); diff --git a/python-checks/src/main/java/org/sonar/python/checks/UndeclaredNameUsageCheck.java b/python-checks/src/main/java/org/sonar/python/checks/UndeclaredNameUsageCheck.java index d70b122c9a..5a90d5e537 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/UndeclaredNameUsageCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/UndeclaredNameUsageCheck.java @@ -50,6 +50,9 @@ public class UndeclaredNameUsageCheck extends PythonSubscriptionCheck { public void initialize(Context context) { context.registerSyntaxNodeConsumer(Tree.Kind.FILE_INPUT, ctx -> { FileInput fileInput = (FileInput) ctx.syntaxNode(); + if (importsManipulatedAllProperty(fileInput)) { + return; + } UnresolvedSymbolsVisitor unresolvedSymbolsVisitor = new UnresolvedSymbolsVisitor(); fileInput.accept(unresolvedSymbolsVisitor); if (!unresolvedSymbolsVisitor.callGlobalsOrLocals && !unresolvedSymbolsVisitor.hasUnresolvedWildcardImport) { @@ -73,6 +76,10 @@ public void initialize(Context context) { }); } + private static boolean importsManipulatedAllProperty(FileInput fileInput) { + return fileInput.globalVariables().stream().anyMatch(s -> s.name().equals("__all__") && s.fullyQualifiedName() != null); + } + private static void checkCfgBlock(CfgBlock cfgBlock, SubscriptionContext ctx, DefinedVariables definedVariables, Set unreachableBlocks, DefinedVariablesAnalysis analysis, List ignoredSymbols) { Map currentState = new HashMap<>(definedVariables.getIn()); diff --git a/python-checks/src/test/java/org/sonar/python/checks/UndeclaredNameUsageCheckTest.java b/python-checks/src/test/java/org/sonar/python/checks/UndeclaredNameUsageCheckTest.java index bf92802f32..d8c5b3cf1b 100644 --- a/python-checks/src/test/java/org/sonar/python/checks/UndeclaredNameUsageCheckTest.java +++ b/python-checks/src/test/java/org/sonar/python/checks/UndeclaredNameUsageCheckTest.java @@ -37,6 +37,13 @@ public void test_wildcard_import() { new UndeclaredNameUsageCheck()); } + @Test + public void test_wildcard_import_all_property() { + PythonCheckVerifier.verifyNoIssue( + Arrays.asList("src/test/resources/checks/undeclaredNameUsageImportWithAll.py", "src/test/resources/checks/undeclaredNameUsageAll/__init__.py"), + new UndeclaredNameUsageCheck()); + } + @Test public void test_unresolved_wildcard_import() { PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/undeclaredNameUsageWithUnresolvedWildcardImport.py", new UndeclaredNameUsageCheck()); diff --git a/python-checks/src/test/resources/checks/undeclaredNameUsageAll/__init__.py b/python-checks/src/test/resources/checks/undeclaredNameUsageAll/__init__.py new file mode 100644 index 0000000000..0f7d8fcfe7 --- /dev/null +++ b/python-checks/src/test/resources/checks/undeclaredNameUsageAll/__init__.py @@ -0,0 +1,5 @@ +# mod.py +def get_exported(): + def f(): pass + return f +__all__ = get_exported() diff --git a/python-checks/src/test/resources/checks/undeclaredNameUsageImportWithAll.py b/python-checks/src/test/resources/checks/undeclaredNameUsageImportWithAll.py new file mode 100644 index 0000000000..59ee527228 --- /dev/null +++ b/python-checks/src/test/resources/checks/undeclaredNameUsageImportWithAll.py @@ -0,0 +1,2 @@ +from undeclaredNameUsageAll import * +f() # OK