Skip to content

Commit 56a87fc

Browse files
SONARPY-2159 Implement VarSymbolToDescriptorConverter
1 parent ff2fb65 commit 56a87fc

File tree

5 files changed

+376
-0
lines changed

5 files changed

+376
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
public class TypeShedConstants {
23+
public static final String BUILTINS_FQN = "builtins";
24+
public static final String BUILTINS_PREFIX = BUILTINS_FQN + ".";
25+
public static final String BUILTINS_TYPE_FQN = BUILTINS_PREFIX + "type";
26+
public static final String BUILTINS_NONE_TYPE_FQN = BUILTINS_PREFIX + "NoneType";
27+
public static final String BUILTINS_TUPLE_FQN = BUILTINS_PREFIX + "tuple";
28+
public static final String BUILTINS_DICT_FQN = BUILTINS_PREFIX + "dict";
29+
30+
private TypeShedConstants() {
31+
}
32+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.Optional;
23+
import java.util.Set;
24+
import java.util.function.Predicate;
25+
import javax.annotation.CheckForNull;
26+
import javax.annotation.Nullable;
27+
import org.sonar.python.types.protobuf.SymbolsProtos;
28+
29+
public class TypeShedUtils {
30+
31+
private TypeShedUtils() {
32+
}
33+
34+
public static String normalizedFqn(String fqn) {
35+
if (fqn.isEmpty()) {
36+
return null;
37+
}
38+
if (fqn.startsWith(TypeShedConstants.BUILTINS_PREFIX)) {
39+
return fqn.substring(TypeShedConstants.BUILTINS_PREFIX.length());
40+
}
41+
return fqn;
42+
}
43+
44+
@CheckForNull
45+
public static String getTypesNormalizedFqn(@Nullable SymbolsProtos.Type type) {
46+
return Optional.ofNullable(type)
47+
.map(TypeShedUtils::getTypesFqn)
48+
.map(TypeShedUtils::normalizedFqn)
49+
.orElse(null);
50+
}
51+
52+
@CheckForNull
53+
private static String getTypesFqn(SymbolsProtos.Type type) {
54+
// Add support CALLABLE and UNION kinds
55+
switch (type.getKind()) {
56+
case INSTANCE:
57+
String typeName = type.getFullyQualifiedName();
58+
// _SpecialForm is the type used for some special types, like Callable, Union, TypeVar, ...
59+
// It comes from CPython impl: https://github.com/python/cpython/blob/e39ae6bef2c357a88e232dcab2e4b4c0f367544b/Lib/typing.py#L439
60+
// This doesn't seem to be very precisely specified in typeshed, because it has special semantic.
61+
// To avoid FPs, we treat it as ANY
62+
if ("typing._SpecialForm".equals(typeName)) {
63+
return null;
64+
}
65+
return typeName.isEmpty() ? null : typeName;
66+
case TYPE_ALIAS:
67+
return getTypesFqn(type.getArgs(0));
68+
case TYPE:
69+
return TypeShedConstants.BUILTINS_TYPE_FQN;
70+
case TUPLE:
71+
return TypeShedConstants.BUILTINS_TUPLE_FQN;
72+
case NONE:
73+
return TypeShedConstants.BUILTINS_NONE_TYPE_FQN;
74+
case TYPED_DICT:
75+
return TypeShedConstants.BUILTINS_DICT_FQN;
76+
case TYPE_VAR:
77+
return Optional.of(type)
78+
.filter(TypeShedUtils::filterTypeVar)
79+
.map(SymbolsProtos.Type::getFullyQualifiedName)
80+
.orElse(null);
81+
default:
82+
return null;
83+
}
84+
}
85+
86+
// ref: SONARPY-1477
87+
private static final Set<String> EXCLUDING_TYPE_VAR_FQN_PATTERNS = Set.of(
88+
"^builtins\\.object$",
89+
"^_ctypes\\._CanCastTo$");
90+
91+
public static boolean filterTypeVar(SymbolsProtos.Type type) {
92+
return Optional.of(type)
93+
// Filtering self returning methods until the SONARPY-1472 will be solved
94+
.filter(Predicate.not(t -> t.getPrettyPrintedName().endsWith(".Self")))
95+
.map(SymbolsProtos.Type::getFullyQualifiedName)
96+
.filter(Predicate.not(String::isEmpty))
97+
.filter(fqn -> EXCLUDING_TYPE_VAR_FQN_PATTERNS.stream().noneMatch(fqn::matches))
98+
.isPresent();
99+
}
100+
101+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 org.sonar.python.index.Descriptor;
23+
import org.sonar.python.index.VariableDescriptor;
24+
import org.sonar.python.types.protobuf.SymbolsProtos;
25+
26+
public class VarSymbolToDescriptorConverter {
27+
28+
public Descriptor convert(SymbolsProtos.VarSymbol varSymbol) {
29+
var fullyQualifiedName = TypeShedUtils.normalizedFqn(varSymbol.getFullyQualifiedName());
30+
var typeAnnotation = TypeShedUtils.getTypesNormalizedFqn(varSymbol.getTypeAnnotation());
31+
return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, typeAnnotation);
32+
}
33+
34+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 org.assertj.core.api.Assertions;
23+
import org.junit.jupiter.api.Test;
24+
import org.sonar.python.types.protobuf.SymbolsProtos;
25+
26+
class TypeShedUtilsTest {
27+
28+
@Test
29+
void normalizedFqnTest() {
30+
Assertions.assertThat(TypeShedUtils.normalizedFqn("builtins.int")).isEqualTo("int");
31+
Assertions.assertThat(TypeShedUtils.normalizedFqn("something.else")).isEqualTo("something.else");
32+
Assertions.assertThat(TypeShedUtils.normalizedFqn("")).isNull();
33+
}
34+
35+
@Test
36+
void getTypesNormalizedFqnBasicTest() {
37+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
38+
.setFullyQualifiedName("builtins.int")
39+
.build()))
40+
.isEqualTo("int");
41+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
42+
.setFullyQualifiedName("something.else")
43+
.build()))
44+
.isEqualTo("something.else");
45+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
46+
.setFullyQualifiedName("")
47+
.build()))
48+
.isNull();
49+
}
50+
51+
@Test
52+
void getTypesNormalizedFqnBasicForInstanceKindTest() {
53+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
54+
.setKind(SymbolsProtos.TypeKind.INSTANCE)
55+
.setFullyQualifiedName("builtins.int")
56+
.build()))
57+
.isEqualTo("int");
58+
59+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
60+
.setKind(SymbolsProtos.TypeKind.INSTANCE)
61+
.setFullyQualifiedName("typing._SpecialForm")
62+
.build()))
63+
.isNull();
64+
65+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
66+
.setKind(SymbolsProtos.TypeKind.INSTANCE)
67+
.setFullyQualifiedName("")
68+
.build()))
69+
.isNull();
70+
}
71+
72+
@Test
73+
void getTypesNormalizedFqnForBuiltinsKindsTest() {
74+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
75+
.setKind(SymbolsProtos.TypeKind.TYPE)
76+
.build()))
77+
.isEqualTo("type");
78+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
79+
.setKind(SymbolsProtos.TypeKind.TUPLE)
80+
.build()))
81+
.isEqualTo("tuple");
82+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
83+
.setKind(SymbolsProtos.TypeKind.NONE)
84+
.build()))
85+
.isEqualTo("NoneType");
86+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
87+
.setKind(SymbolsProtos.TypeKind.TYPED_DICT)
88+
.build()))
89+
.isEqualTo("dict");
90+
}
91+
92+
@Test
93+
void getTypesNormalizedFqnForTypeAliasKindTest() {
94+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
95+
.setKind(SymbolsProtos.TypeKind.TYPE_ALIAS)
96+
.addArgs(
97+
SymbolsProtos.Type.newBuilder()
98+
.setKind(SymbolsProtos.TypeKind.INSTANCE)
99+
.setFullyQualifiedName("builtins.int")
100+
.build()
101+
)
102+
.build()))
103+
.isEqualTo("int");
104+
}
105+
106+
@Test
107+
void getTypesNormalizedFqnForTypeVarKindTest() {
108+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
109+
.setKind(SymbolsProtos.TypeKind.TYPE_VAR)
110+
.setFullyQualifiedName("builtins.int")
111+
.build()))
112+
.isEqualTo("int");
113+
114+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
115+
.setKind(SymbolsProtos.TypeKind.TYPE_VAR)
116+
.setFullyQualifiedName("builtins.object")
117+
.build()))
118+
.isNull();
119+
120+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
121+
.setKind(SymbolsProtos.TypeKind.TYPE_VAR)
122+
.setFullyQualifiedName("_ctypes._CanCastTo")
123+
.build()))
124+
.isNull();
125+
}
126+
127+
@Test
128+
void getTypesNormalizedFqnForUnsupportedKindsTest() {
129+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
130+
.setKind(SymbolsProtos.TypeKind.CALLABLE)
131+
.build()))
132+
.isNull();
133+
134+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
135+
.setKind(SymbolsProtos.TypeKind.UNION)
136+
.build()))
137+
.isNull();
138+
139+
Assertions.assertThat(TypeShedUtils.getTypesNormalizedFqn(SymbolsProtos.Type.newBuilder()
140+
.setKind(SymbolsProtos.TypeKind.LITERAL)
141+
.build()))
142+
.isNull();
143+
}
144+
145+
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 org.assertj.core.api.Assertions;
23+
import org.junit.jupiter.api.Test;
24+
import org.sonar.python.index.VariableDescriptor;
25+
import org.sonar.python.types.protobuf.SymbolsProtos;
26+
27+
class VarSymbolToDescriptorConverterTest {
28+
29+
@Test
30+
void test() {
31+
var symbol = SymbolsProtos.VarSymbol.newBuilder()
32+
.setName("something")
33+
.setFullyQualifiedName("module.something")
34+
.setTypeAnnotation(SymbolsProtos.Type.newBuilder()
35+
.setFullyQualifiedName("module.something_else")
36+
.build())
37+
.build();
38+
var converter = new VarSymbolToDescriptorConverter();
39+
40+
var descriptor = (VariableDescriptor) converter.convert(symbol);
41+
Assertions.assertThat(descriptor.name()).isEqualTo("something");
42+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("module.something");
43+
Assertions.assertThat(descriptor.annotatedType()).isEqualTo("module.something_else");
44+
}
45+
46+
@Test
47+
void builtinVarTest() {
48+
var symbol = SymbolsProtos.VarSymbol.newBuilder()
49+
.setName("int")
50+
.setFullyQualifiedName("builtins.int")
51+
.setTypeAnnotation(SymbolsProtos.Type.newBuilder()
52+
.setFullyQualifiedName("builtins.int")
53+
.build())
54+
.build();
55+
var converter = new VarSymbolToDescriptorConverter();
56+
57+
var descriptor = (VariableDescriptor) converter.convert(symbol);
58+
Assertions.assertThat(descriptor.name()).isEqualTo("int");
59+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("int");
60+
Assertions.assertThat(descriptor.annotatedType()).isEqualTo("int");
61+
}
62+
63+
}

0 commit comments

Comments
 (0)