diff --git a/python-checks/src/test/resources/checks/useOfAnyAsTypeHintCheck/useOfAnyAsTypeHintImporting.py b/python-checks/src/test/resources/checks/useOfAnyAsTypeHintCheck/useOfAnyAsTypeHintImporting.py index 3bce877013..597b555f7a 100644 --- a/python-checks/src/test/resources/checks/useOfAnyAsTypeHintCheck/useOfAnyAsTypeHintImporting.py +++ b/python-checks/src/test/resources/checks/useOfAnyAsTypeHintCheck/useOfAnyAsTypeHintImporting.py @@ -12,5 +12,5 @@ class ImportedWithoutMetaClassInherited(ImportedParentWithoutMetaClass): def imported_inherited_foo(self) -> Any: # Noncompliant ... class ImportedWithMetaClassInherited(ImportedParentWithMetaClass): - def imported_inherited_foo(self) -> Any: # FN SONARPY-2331 + def imported_inherited_foo(self) -> Any: # Noncompliant ... diff --git a/python-frontend/src/main/java/org/sonar/python/index/ClassDescriptor.java b/python-frontend/src/main/java/org/sonar/python/index/ClassDescriptor.java index 496d49a1d3..342fa73d65 100644 --- a/python-frontend/src/main/java/org/sonar/python/index/ClassDescriptor.java +++ b/python-frontend/src/main/java/org/sonar/python/index/ClassDescriptor.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.plugins.python.api.LocationInFile; @@ -94,6 +95,7 @@ public boolean hasMetaClass() { return hasMetaClass; } + @CheckForNull public String metaclassFQN() { return metaclassFQN; } diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverter.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverter.java index 75f54cfd8b..3d1a265713 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverter.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverter.java @@ -129,6 +129,12 @@ private static Descriptor convert(String moduleFqn, String parentFqn, String sym } } + var metaclassFQN = type.metaClasses() + .stream() + .map(metaClass -> typeFqn(moduleFqn, metaClass)) + .findFirst() + .orElse(null); + return new ClassDescriptor(symbolName, symbolFqn, superClasses, memberDescriptors, @@ -136,7 +142,7 @@ private static Descriptor convert(String moduleFqn, String parentFqn, String sym type.definitionLocation().orElse(null), hasSuperClassWithoutDescriptor, type.hasMetaClass(), - null, + metaclassFQN, 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 b80fdb18b2..a513428d46 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 @@ -1125,6 +1125,44 @@ class Field(MetaField): ... assertThat(symbol.hasUnresolvedTypeHierarchy()).isTrue(); } + @Test + void class_wth_imported_metaclass() { + var code = """ + from abc import ABCMeta + class WithMetaclass(metaclass=ABCMeta): ... + """; + + var projectSymbolTable = new ProjectLevelSymbolTable(); + projectSymbolTable.addModule(parseWithoutSymbols(code), "", pythonFile("mod.py")); + var symbol = (ClassSymbolImpl) projectSymbolTable.getSymbol("mod.WithMetaclass"); + assertThat(symbol.metaclassFQN()).isEqualTo("abc.ABCMeta"); + } + + @Test + void class_wth_locally_defined_metaclass() { + var code = """ + class LocalMetaClass: ... + class WithMetaclass(metaclass=LocalMetaClass): ... + """; + + var projectSymbolTable = new ProjectLevelSymbolTable(); + projectSymbolTable.addModule(parseWithoutSymbols(code), "", pythonFile("mod.py")); + var symbol = (ClassSymbolImpl) projectSymbolTable.getSymbol("mod.WithMetaclass"); + assertThat(symbol.metaclassFQN()).isEqualTo("mod.LocalMetaClass"); + } + + @Test + void class_wth_unresolved_import_metaclass() { + var code = """ + from unknown import UnresolvedMetaClass + class WithMetaclass(metaclass=UnresolvedMetaClass): ... + """; + + var projectSymbolTable = new ProjectLevelSymbolTable(); + projectSymbolTable.addModule(parseWithoutSymbols(code), "", pythonFile("mod.py")); + var symbol = (ClassSymbolImpl) projectSymbolTable.getSymbol("mod.WithMetaclass"); + assertThat(symbol.metaclassFQN()).isEqualTo("unknown.UnresolvedMetaClass"); + } @Test void projectPackages() { diff --git a/python-frontend/src/test/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverterTest.java b/python-frontend/src/test/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverterTest.java index 2528f98689..0c68f7de37 100644 --- a/python-frontend/src/test/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverterTest.java +++ b/python-frontend/src/test/java/org/sonar/python/semantic/v2/converter/PythonTypeToDescriptorConverterTest.java @@ -134,8 +134,7 @@ void testConvertClassType() { // SONARPY-2307 support for superClass is missing in ClassType assertThat(classDescriptor.hasSuperClassWithoutDescriptor()).isFalse(); - // SONARPY-2307 support for metaclassFQN is missing in ClassType - assertThat(classDescriptor.metaclassFQN()).isNull(); + assertThat(classDescriptor.metaclassFQN()).isEqualTo("int"); // SONARPY-2307 support for generics is missing in ClassType assertThat(classDescriptor.supportsGenerics()).isFalse(); }