Skip to content

Commit 7fc2ede

Browse files
SONARPY-2127 Implement ClassDescriptorToPythonTypeConverter (#1969)
1 parent 5621e77 commit 7fc2ede

File tree

5 files changed

+149
-5
lines changed

5 files changed

+149
-5
lines changed

python-frontend/src/main/java/org/sonar/python/semantic/v2/ClassTypeBuilder.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,16 @@ public ClassTypeBuilder withDefinitionLocation(@Nullable LocationInFile definiti
6464
return this;
6565
}
6666

67-
public List<TypeWrapper> superClasses() {
68-
return superClasses;
69-
}
70-
7167
public ClassTypeBuilder addSuperClass(PythonType type) {
7268
superClasses.add(new LazyTypeWrapper(type));
7369
return this;
7470
}
7571

72+
public ClassTypeBuilder addSuperClass(TypeWrapper typeWrapper) {
73+
superClasses.add(typeWrapper);
74+
return this;
75+
}
76+
7677
public ClassTypeBuilder withSuperClasses(PythonType... types) {
7778
Arrays.stream(types).forEach(this::addSuperClass);
7879
return this;

python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/AnyDescriptorToPythonTypeConverter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.sonar.python.semantic.v2.converter;
2121

22+
import java.util.EnumMap;
2223
import java.util.Map;
2324
import org.sonar.python.index.Descriptor;
2425
import org.sonar.python.semantic.v2.LazyTypesContext;
@@ -32,7 +33,9 @@ public class AnyDescriptorToPythonTypeConverter {
3233

3334
public AnyDescriptorToPythonTypeConverter(LazyTypesContext lazyTypesContext) {
3435
this.lazyTypesContext = lazyTypesContext;
35-
converters = Map.of();
36+
converters = new EnumMap<>(Map.of(
37+
Descriptor.Kind.CLASS, new ClassDescriptorToPythonTypeConverter()
38+
));
3639
}
3740

3841
public PythonType convert(Descriptor from) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.semantic.v2.converter;
21+
22+
import org.sonar.python.index.ClassDescriptor;
23+
import org.sonar.python.index.Descriptor;
24+
import org.sonar.python.semantic.v2.ClassTypeBuilder;
25+
import org.sonar.python.types.v2.LazyTypeWrapper;
26+
import org.sonar.python.types.v2.Member;
27+
import org.sonar.python.types.v2.PythonType;
28+
29+
public class ClassDescriptorToPythonTypeConverter implements DescriptorToPythonTypeConverter {
30+
31+
private static PythonType convert(ConversionContext ctx, ClassDescriptor from) {
32+
var typeBuilder = new ClassTypeBuilder()
33+
.withName(from.name())
34+
.withDefinitionLocation(from.definitionLocation());
35+
36+
from.superClasses().stream()
37+
.map(fqn -> ctx.lazyTypesContext().getOrCreateLazyType(fqn))
38+
.map(LazyTypeWrapper::new)
39+
.forEach(typeBuilder::addSuperClass);
40+
41+
var type = typeBuilder.build();
42+
ctx.pushParent(type);
43+
from.members()
44+
.stream()
45+
.map(d -> new Member(d.name(), ctx.convert(d)))
46+
.forEach(type.members()::add);
47+
ctx.pollParent();
48+
return type;
49+
}
50+
51+
@Override
52+
public PythonType convert(ConversionContext ctx, Descriptor from) {
53+
if (from instanceof ClassDescriptor classDescriptor) {
54+
return convert(ctx, classDescriptor);
55+
}
56+
throw new IllegalArgumentException("Unsupported Descriptor");
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.semantic.v2.converter;
21+
22+
import org.assertj.core.api.Assertions;
23+
import org.junit.jupiter.api.Test;
24+
import org.mockito.Mockito;
25+
import org.sonar.python.index.FunctionDescriptor;
26+
27+
class ClassDescriptorToPythonTypeConverterTest {
28+
@Test
29+
void unsupportedClassTest() {
30+
var ctx = Mockito.mock(ConversionContext.class);
31+
var descriptor = Mockito.mock(FunctionDescriptor.class);
32+
var converter = new ClassDescriptorToPythonTypeConverter();
33+
Assertions.assertThatThrownBy(() -> converter.convert(ctx, descriptor))
34+
.isInstanceOf(IllegalArgumentException.class)
35+
.hasMessage("Unsupported Descriptor");
36+
}
37+
}

python-frontend/src/test/java/org/sonar/python/semantic/v2/converter/DescriptorToPythonTypeConverterTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@
1919
*/
2020
package org.sonar.python.semantic.v2.converter;
2121

22+
import java.util.List;
2223
import org.assertj.core.api.Assertions;
2324
import org.junit.jupiter.api.Test;
2425
import org.mockito.Mockito;
2526
import org.sonar.python.index.AmbiguousDescriptor;
27+
import org.sonar.python.index.ClassDescriptor;
2628
import org.sonar.python.index.Descriptor;
29+
import org.sonar.python.semantic.v2.ClassTypeBuilder;
2730
import org.sonar.python.semantic.v2.LazyTypesContext;
31+
import org.sonar.python.types.v2.ClassType;
32+
import org.sonar.python.types.v2.LazyType;
33+
import org.sonar.python.types.v2.Member;
2834
import org.sonar.python.types.v2.PythonType;
35+
import org.sonar.python.types.v2.TypeWrapper;
2936

3037
class DescriptorToPythonTypeConverterTest {
3138

@@ -50,6 +57,44 @@ void ambiguousDescriptorConversionTest() {
5057
Assertions.assertThat(type).isEqualTo(PythonType.UNKNOWN);
5158
}
5259

60+
@Test
61+
void classDescriptorConversionTest() {
62+
var lazyTypesContext = Mockito.mock(LazyTypesContext.class);
63+
var converter = new AnyDescriptorToPythonTypeConverter(lazyTypesContext);
64+
var descriptor = Mockito.mock(ClassDescriptor.class);
65+
66+
var parentClassName = "Parent";
67+
var resolvedParent = new ClassTypeBuilder().withName(parentClassName).build();
68+
69+
var member = Mockito.mock(AmbiguousDescriptor.class);
70+
Mockito.when(member.kind()).thenReturn(Descriptor.Kind.AMBIGUOUS);
71+
Mockito.when(member.name()).thenReturn("member");
72+
73+
Mockito.when(descriptor.kind()).thenReturn(Descriptor.Kind.CLASS);
74+
Mockito.when(descriptor.name()).thenReturn("Sample");
75+
Mockito.when(descriptor.superClasses()).thenReturn(List.of(parentClassName));
76+
Mockito.when(descriptor.members()).thenReturn(List.of(
77+
member
78+
));
79+
Mockito.when(lazyTypesContext.getOrCreateLazyType(parentClassName))
80+
.thenReturn(new LazyType(parentClassName, lazyTypesContext));
81+
82+
Mockito.when(lazyTypesContext.resolveLazyType(Mockito.argThat(lt -> parentClassName.equals(lt.fullyQualifiedName()))))
83+
.thenReturn(resolvedParent);
84+
85+
var type = (ClassType) converter.convert(descriptor);
86+
Assertions.assertThat(type.name()).isEqualTo("Sample");
87+
88+
Assertions.assertThat(type.superClasses())
89+
.extracting(TypeWrapper.class::cast)
90+
.extracting(TypeWrapper::type)
91+
.containsOnly(resolvedParent);
92+
93+
Assertions.assertThat(type.members())
94+
.extracting(Member::name)
95+
.containsOnly("member");
96+
}
97+
5398

5499

55100
}

0 commit comments

Comments
 (0)