Skip to content

Commit 158a80f

Browse files
SONARPY-2162 Implement ModuleSymbolToDescriptorConverter
1 parent 7bfb947 commit 158a80f

File tree

5 files changed

+304
-0
lines changed

5 files changed

+304
-0
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/ProjectPythonVersion.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonar.plugins.python.api;
2121

2222
import java.util.Set;
23+
import java.util.stream.Collectors;
2324

2425
import static org.sonar.plugins.python.api.PythonVersionUtils.Version;
2526
import static org.sonar.plugins.python.api.PythonVersionUtils.allVersions;
@@ -39,4 +40,9 @@ public static void setCurrentVersions(Set<Version> currentVersions) {
3940
ProjectPythonVersion.currentVersions = currentVersions;
4041
}
4142

43+
public static Set<String> currentVersionValues() {
44+
return currentVersions().stream()
45+
.map(PythonVersionUtils.Version::serializedValue)
46+
.collect(Collectors.toSet());
47+
}
4248
}

python-frontend/src/main/java/org/sonar/python/index/Descriptor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public interface Descriptor {
3131
Kind kind();
3232

3333
enum Kind {
34+
MODULE,
3435
FUNCTION,
3536
CLASS,
3637
VARIABLE,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.index;
21+
22+
import java.util.Map;
23+
import javax.annotation.CheckForNull;
24+
import javax.annotation.Nullable;
25+
26+
public class ModuleDescriptor implements Descriptor{
27+
private final String name;
28+
private final String fullyQualifiedName;
29+
private final Map<String, Descriptor> members;
30+
31+
public ModuleDescriptor(String name, @Nullable String fullyQualifiedName, Map<String, Descriptor> members) {
32+
this.name = name;
33+
this.fullyQualifiedName = fullyQualifiedName;
34+
this.members = members;
35+
}
36+
37+
@Override
38+
public String name() {
39+
return name;
40+
}
41+
42+
@CheckForNull
43+
@Override
44+
public String fullyQualifiedName() {
45+
return fullyQualifiedName;
46+
}
47+
48+
public Map<String, Descriptor> members() {
49+
return members;
50+
}
51+
52+
@Override
53+
public Kind kind() {
54+
return Kind.MODULE;
55+
}
56+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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.typeshed;
21+
22+
import java.util.HashMap;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
27+
import javax.annotation.CheckForNull;
28+
import javax.annotation.Nullable;
29+
import org.sonar.plugins.python.api.ProjectPythonVersion;
30+
import org.sonar.plugins.python.api.PythonVersionUtils;
31+
import org.sonar.python.index.AmbiguousDescriptor;
32+
import org.sonar.python.index.Descriptor;
33+
import org.sonar.python.index.ModuleDescriptor;
34+
import org.sonar.python.types.protobuf.SymbolsProtos;
35+
36+
public class ModuleSymbolToDescriptorConverter {
37+
private final ClassSymbolToDescriptorConverter classConverter;
38+
private final FunctionSymbolToDescriptorConverter functionConverter;
39+
private final VarSymbolToDescriptorConverter variableConverter;
40+
private final OverloadedFunctionSymbolToDescriptorConverter overloadedFunctionConverter;
41+
private final Set<String> supportedPythonVersions;
42+
43+
public ModuleSymbolToDescriptorConverter() {
44+
functionConverter = new FunctionSymbolToDescriptorConverter();
45+
variableConverter = new VarSymbolToDescriptorConverter();
46+
overloadedFunctionConverter = new OverloadedFunctionSymbolToDescriptorConverter(functionConverter);
47+
classConverter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter);
48+
supportedPythonVersions = ProjectPythonVersion.currentVersionValues();
49+
}
50+
51+
@CheckForNull
52+
public ModuleDescriptor convert(@Nullable SymbolsProtos.ModuleSymbol moduleSymbol) {
53+
if (moduleSymbol == null) {
54+
return null;
55+
}
56+
57+
var name = moduleSymbol.getFullyQualifiedName();
58+
var fullyQualifiedName = moduleSymbol.getFullyQualifiedName();
59+
var members = getModuleDescriptors(moduleSymbol);
60+
61+
return new ModuleDescriptor(name, fullyQualifiedName, members);
62+
}
63+
64+
private Map<String, Descriptor> getModuleDescriptors(SymbolsProtos.ModuleSymbol moduleSymbol) {
65+
Map<String, Set<Descriptor>> protoSymbolsByName = new HashMap<>();
66+
moduleSymbol.getClassesList()
67+
.stream()
68+
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
69+
.map(classConverter::convert)
70+
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
71+
moduleSymbol.getFunctionsList()
72+
.stream()
73+
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
74+
.map(functionConverter::convert)
75+
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
76+
moduleSymbol.getOverloadedFunctionsList()
77+
.stream()
78+
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
79+
.map(overloadedFunctionConverter::convert)
80+
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
81+
moduleSymbol.getVarsList()
82+
.stream()
83+
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
84+
.map(variableConverter::convert)
85+
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
86+
87+
var descriptorsByName = new HashMap<String, Descriptor>();
88+
89+
protoSymbolsByName.forEach((name, descriptors) -> {
90+
Descriptor disambiguatedDescriptor = disambiguateSymbolsWithSameName(descriptors);
91+
descriptorsByName.put(name, disambiguatedDescriptor);
92+
});
93+
94+
return descriptorsByName;
95+
}
96+
97+
private static Descriptor disambiguateSymbolsWithSameName(Set<Descriptor> descriptors) {
98+
if (descriptors.size() > 1) {
99+
return AmbiguousDescriptor.create(descriptors);
100+
}
101+
return descriptors.iterator().next();
102+
}
103+
104+
private boolean isValidForProjectPythonVersion(List<String> validForPythonVersions) {
105+
if (validForPythonVersions.isEmpty()) {
106+
return true;
107+
}
108+
if (supportedPythonVersions.stream().allMatch(PythonVersionUtils.Version.V_312.serializedValue()::equals)
109+
&& validForPythonVersions.contains(PythonVersionUtils.Version.V_311.serializedValue())) {
110+
return true;
111+
}
112+
HashSet<String> intersection = new HashSet<>(validForPythonVersions);
113+
intersection.retainAll(supportedPythonVersions);
114+
return !intersection.isEmpty();
115+
}
116+
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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.typeshed;
21+
22+
import java.util.Set;
23+
import org.assertj.core.api.Assertions;
24+
import org.junit.jupiter.api.Test;
25+
import org.sonar.plugins.python.api.ProjectPythonVersion;
26+
import org.sonar.plugins.python.api.PythonVersionUtils;
27+
import org.sonar.python.index.AmbiguousDescriptor;
28+
import org.sonar.python.index.ClassDescriptor;
29+
import org.sonar.python.index.FunctionDescriptor;
30+
import org.sonar.python.index.VariableDescriptor;
31+
import org.sonar.python.types.protobuf.SymbolsProtos;
32+
33+
class ModuleSymbolToDescriptorConverterTest {
34+
35+
@Test
36+
void test() {
37+
var converter = new ModuleSymbolToDescriptorConverter();
38+
var symbol = SymbolsProtos.ModuleSymbol.newBuilder()
39+
.setFullyQualifiedName("module")
40+
.addVars(SymbolsProtos.VarSymbol.newBuilder()
41+
.setName("v1")
42+
.setFullyQualifiedName("module.v1")
43+
.build())
44+
.addVars(SymbolsProtos.VarSymbol.newBuilder()
45+
.setName("v2")
46+
.setFullyQualifiedName("module.v2")
47+
.build())
48+
.addVars(SymbolsProtos.VarSymbol.newBuilder()
49+
.setName("v2")
50+
.setFullyQualifiedName("module.v2")
51+
.build())
52+
.addFunctions(SymbolsProtos.FunctionSymbol.newBuilder()
53+
.setName("foo")
54+
.setFullyQualifiedName("module.foo")
55+
.build())
56+
.addOverloadedFunctions(SymbolsProtos.OverloadedFunctionSymbol.newBuilder()
57+
.setName("overloaded_foo")
58+
.setFullname("module.overloaded_foo")
59+
.addDefinitions(SymbolsProtos.FunctionSymbol.newBuilder()
60+
.setName("overloaded_foo")
61+
.setFullyQualifiedName("module.overloaded_foo")
62+
.build())
63+
.addDefinitions(SymbolsProtos.FunctionSymbol.newBuilder()
64+
.setName("overloaded_foo")
65+
.setFullyQualifiedName("module.overloaded_foo")
66+
.build())
67+
.build()
68+
)
69+
.addClasses(
70+
SymbolsProtos.ClassSymbol.newBuilder()
71+
.setName("MyClass")
72+
.setFullyQualifiedName("module.MyClass")
73+
.build()
74+
)
75+
.build();
76+
var descriptor = converter.convert(symbol);
77+
Assertions.assertThat(descriptor).isNotNull();
78+
Assertions.assertThat(descriptor.name()).isEqualTo("module");
79+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("module");
80+
Assertions.assertThat(descriptor.members().get("v1")).isInstanceOf(VariableDescriptor.class);
81+
Assertions.assertThat(descriptor.members().get("v2")).isInstanceOf(AmbiguousDescriptor.class);
82+
Assertions.assertThat(descriptor.members().get("foo")).isInstanceOf(FunctionDescriptor.class);
83+
Assertions.assertThat(descriptor.members().get("overloaded_foo")).isInstanceOf(AmbiguousDescriptor.class);
84+
Assertions.assertThat(descriptor.members().get("MyClass")).isInstanceOf(ClassDescriptor.class);
85+
}
86+
87+
@Test
88+
void nullSymbolTest() {
89+
var converter = new ModuleSymbolToDescriptorConverter();
90+
var descriptor = converter.convert(null);
91+
Assertions.assertThat(descriptor).isNull();
92+
}
93+
94+
@Test
95+
void validForPythonVersionsTest() {
96+
ProjectPythonVersion.setCurrentVersions(Set.of(PythonVersionUtils.Version.V_312));
97+
var converter = new ModuleSymbolToDescriptorConverter();
98+
var symbol = SymbolsProtos.ModuleSymbol.newBuilder()
99+
.setFullyQualifiedName("module")
100+
.addVars(SymbolsProtos.VarSymbol.newBuilder()
101+
.setName("v1")
102+
.setFullyQualifiedName("module.v1")
103+
.addValidFor("311")
104+
.build())
105+
.addVars(SymbolsProtos.VarSymbol.newBuilder()
106+
.setName("v2")
107+
.setFullyQualifiedName("module.v2")
108+
.addValidFor("39")
109+
.build())
110+
.build();
111+
var descriptor = converter.convert(symbol);
112+
Assertions.assertThat(descriptor).isNotNull();
113+
Assertions.assertThat(descriptor.members().get("v1")).isInstanceOf(VariableDescriptor.class);
114+
Assertions.assertThat(descriptor.members().get("v2")).isNull();
115+
116+
ProjectPythonVersion.setCurrentVersions(Set.of(PythonVersionUtils.Version.V_39));
117+
converter = new ModuleSymbolToDescriptorConverter();
118+
descriptor = converter.convert(symbol);
119+
Assertions.assertThat(descriptor).isNotNull();
120+
Assertions.assertThat(descriptor.members().get("v1")).isNull();
121+
Assertions.assertThat(descriptor.members().get("v2")).isInstanceOf(VariableDescriptor.class);
122+
}
123+
124+
}

0 commit comments

Comments
 (0)