diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/ProjectLevelSymbolTable.java b/python-frontend/src/main/java/org/sonar/python/semantic/ProjectLevelSymbolTable.java index 3910245c90..93d9fb4781 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/ProjectLevelSymbolTable.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/ProjectLevelSymbolTable.java @@ -49,6 +49,7 @@ import org.sonar.python.types.v2.FunctionType; import org.sonar.python.types.v2.PythonType; import org.sonar.python.types.v2.TriBool; +import org.sonar.python.types.v2.TypeCheckBuilder; import org.sonar.python.types.v2.TypeChecker; import org.sonar.python.types.v2.UnknownType; @@ -148,7 +149,7 @@ public Symbol getSymbol(@Nullable String fullyQualifiedName, @Nullable String lo } public Symbol getSymbol(@Nullable String fullyQualifiedName, @Nullable String localSymbolName, - Map createdSymbolsByDescriptor, Map createdSymbolsByFqn) { + Map createdSymbolsByDescriptor, Map createdSymbolsByFqn) { if (fullyQualifiedName == null) return null; Descriptor descriptor = globalDescriptorsByFQN().get(fullyQualifiedName); return descriptor == null ? null : DescriptorUtils.symbolFromDescriptor(descriptor, this, localSymbolName, createdSymbolsByDescriptor, createdSymbolsByFqn); @@ -226,11 +227,21 @@ public Collection stubFilesSymbols() { private class DjangoViewsVisitor extends BaseTreeVisitor { String fullyQualifiedModuleName; + private TypeCheckBuilder confPathCall = null; + private TypeCheckBuilder pathCall = null; public DjangoViewsVisitor(String fullyQualifiedModuleName) { this.fullyQualifiedModuleName = fullyQualifiedModuleName; } + @Override + public void visitFileInput(FileInput fileInput) { + TypeChecker typeChecker = new TypeChecker(new BasicTypeTable(new ProjectLevelTypeTable(ProjectLevelSymbolTable.this))); + confPathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.conf.path"); + pathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.path"); + super.visitFileInput(fileInput); + } + @Override public void visitCallExpression(CallExpression callExpression) { super.visitCallExpression(callExpression); @@ -249,24 +260,9 @@ public void visitCallExpression(CallExpression callExpression) { } private boolean isCallRegisteringDjangoView(CallExpression callExpression) { - if (!isCallToPath(callExpression)) { - return false; - } - TypeChecker typeChecker = new TypeChecker(new BasicTypeTable(new ProjectLevelTypeTable(ProjectLevelSymbolTable.this))); - TriBool isConfPathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.conf.path").check(callExpression.callee().typeV2()); - TriBool isPathCall = typeChecker.typeCheckBuilder().isTypeWithName("django.urls.path").check(callExpression.callee().typeV2()); + TriBool isConfPathCall = confPathCall.check(callExpression.callee().typeV2()); + TriBool isPathCall = pathCall.check(callExpression.callee().typeV2()); return isConfPathCall.equals(TriBool.TRUE) || isPathCall.equals(TriBool.TRUE); } - - private boolean isCallToPath(CallExpression callExpression) { - Expression callee = callExpression.callee(); - if (callee instanceof QualifiedExpression qualifiedExpression) { - return qualifiedExpression.name().name().equals("path"); - } - if (callee instanceof Name name) { - return name.name().equals("path"); - } - return false; - } } } diff --git a/python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java b/python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java index dd6aaa7655..0dadb082c9 100644 --- a/python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java +++ b/python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java @@ -521,7 +521,7 @@ void importedStubModules() { """); ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty(); projectLevelSymbolTable.addModule(tree, "my_package", pythonFile("mod.py")); - assertThat(projectLevelSymbolTable.typeShedDescriptorsProvider().stubModules()).containsExactlyInAnyOrder("math", "os"); + assertThat(projectLevelSymbolTable.typeShedDescriptorsProvider().stubModules()).containsExactlyInAnyOrder("math", "os", "django", "django.urls.conf", "django.urls"); } @Test diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java index 1c321e7cda..06406c22af 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java @@ -891,6 +891,7 @@ void test_typeshed_stubs_information_is_saved_to_cache() { // typing comes from TypeCheckBuilder querying the ProjectLevelType table (by looking for TypeVar) in its checks, which then queries & cache info in TypeShedDescriptorsProvider assertThat(resolvedTypeshedModules).containsExactlyInAnyOrder( "typing", "math", + "django", "django.urls.conf", "django.urls", "fastapi", "fastapi.responses" ); } @@ -1112,7 +1113,7 @@ void write_cpd_tokens_to_cache() throws IOException { assertThat(writeCache.getData().keySet()).containsExactlyInAnyOrder( "python:cache_version", "python:files", "python:descriptors:moduleKey:pass.py", "python:imports:moduleKey:pass.py", - "python:cpd:data:moduleKey:pass.py", "python:cpd:stringTable:moduleKey:pass.py", "python:content_hashes:moduleKey:pass.py"); + "python:cpd:data:moduleKey:pass.py", "python:cpd:stringTable:moduleKey:pass.py", "python:content_hashes:moduleKey:pass.py", "python:typeshed_modules"); byte[] tokenData = writeCache.getData().get("python:cpd:data:moduleKey:pass.py"); byte[] stringTable = writeCache.getData().get("python:cpd:stringTable:moduleKey:pass.py");