Skip to content

Commit dbf6c17

Browse files
SONARPY-945 Use precomputed Typeshed symbols for custom stub files
1 parent 826d1bc commit dbf6c17

File tree

7 files changed

+69
-91
lines changed

7 files changed

+69
-91
lines changed

python-frontend/src/main/java/org/sonar/python/types/TypeShed.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
import static org.sonar.plugins.python.api.types.BuiltinTypes.NONE_TYPE;
5959
import static org.sonar.plugins.python.api.types.BuiltinTypes.STR;
6060
import static org.sonar.plugins.python.api.types.BuiltinTypes.TUPLE;
61-
import static org.sonar.python.types.TypeShedThirdParties.getModuleSymbols;
6261

6362
public class TypeShed {
6463

@@ -71,8 +70,9 @@ public class TypeShed {
7170
private static final String THIRD_PARTY_2 = "typeshed/third_party/2/";
7271
private static final String THIRD_PARTY_3 = "typeshed/third_party/3/";
7372
private static final String CUSTOM_THIRD_PARTY = "custom/";
74-
private static final String PROTOBUF = "protobuf/";
75-
private static final String PROTOBUF_THIRD_PARTY = "protobuf/stubs/";
73+
private static final String PROTOBUF_CUSTOM_STUBS = "custom_protobuf/";
74+
private static final String PROTOBUF = "stdlib_protobuf/";
75+
private static final String PROTOBUF_THIRD_PARTY = "third_party_protobuf/";
7676
private static final String BUILTINS_FQN = "builtins";
7777
private static final String BUILTINS_PREFIX = BUILTINS_FQN + ".";
7878
// Those fundamentals builtins symbols need not to be ambiguous for the frontend to work properly
@@ -96,7 +96,7 @@ private TypeShed() {
9696
public static Map<String, Symbol> builtinSymbols() {
9797
if ((TypeShed.builtins == null)) {
9898
supportedPythonVersions = ProjectPythonVersion.currentVersions().stream().map(PythonVersionUtils.Version::serializedValue).collect(Collectors.toSet());
99-
Map<String, Symbol> builtins = getSymbolsFromProtobufModule(BUILTINS_FQN, false);
99+
Map<String, Symbol> builtins = getSymbolsFromProtobufModule(BUILTINS_FQN, PROTOBUF);
100100
builtins.put(NONE_TYPE, new ClassSymbolImpl(NONE_TYPE, NONE_TYPE));
101101
TypeShed.builtins = Collections.unmodifiableMap(builtins);
102102
TypeShed.builtinGlobalSymbols.put("", new HashSet<>(builtins.values()));
@@ -266,18 +266,18 @@ private static Map<String, Symbol> searchTypeShedForModule(String moduleName) {
266266
return new HashMap<>();
267267
}
268268
modulesInProgress.add(moduleName);
269-
Map<String, Symbol> customSymbols = getModuleSymbols(moduleName, CUSTOM_THIRD_PARTY, builtinGlobalSymbols);
269+
Map<String, Symbol> customSymbols = getSymbolsFromProtobufModule(moduleName, PROTOBUF_CUSTOM_STUBS);
270270
if (!customSymbols.isEmpty()) {
271271
modulesInProgress.remove(moduleName);
272272
return customSymbols;
273273
}
274-
Map<String, Symbol> symbolsFromProtobuf = getSymbolsFromProtobufModule(moduleName, false);
274+
Map<String, Symbol> symbolsFromProtobuf = getSymbolsFromProtobufModule(moduleName, PROTOBUF);
275275
if (!symbolsFromProtobuf.isEmpty()) {
276276
modulesInProgress.remove(moduleName);
277277
return symbolsFromProtobuf;
278278
}
279279

280-
Map<String, Symbol> thirdPartySymbols = getSymbolsFromProtobufModule(moduleName, true);
280+
Map<String, Symbol> thirdPartySymbols = getSymbolsFromProtobufModule(moduleName, PROTOBUF_THIRD_PARTY);
281281
modulesInProgress.remove(moduleName);
282282
return thirdPartySymbols;
283283
}
@@ -308,9 +308,8 @@ private static boolean isAmbiguousSymbolOfClasses(Symbol symbol) {
308308
return false;
309309
}
310310

311-
private static Map<String, Symbol> getSymbolsFromProtobufModule(String moduleName, boolean isThirdParty) {
312-
String protobufDir = isThirdParty ? PROTOBUF_THIRD_PARTY : PROTOBUF;
313-
InputStream resource = TypeShed.class.getResourceAsStream(protobufDir + moduleName + ".protobuf");
311+
private static Map<String, Symbol> getSymbolsFromProtobufModule(String moduleName, String dirName) {
312+
InputStream resource = TypeShed.class.getResourceAsStream(dirName + moduleName + ".protobuf");
314313
if (resource == null) {
315314
return Collections.emptyMap();
316315
}

python-frontend/src/test/java/org/sonar/python/types/CustomStubSanityTest.java

Lines changed: 0 additions & 75 deletions
This file was deleted.

python-frontend/src/test/java/org/sonar/python/types/TypeShedTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,15 @@ public void package_django_class_property_type() {
264264
assertThat(((ClassSymbol) requestSymbol).declaredMembers().iterator().next().annotatedTypeName()).isEqualTo("django.http.request.QueryDict");
265265
}
266266

267+
@Test
268+
public void package_lxml_reexported_symbol_fqn() {
269+
Map<String, Symbol> lxmlEtreeSymbols = symbolsForModule("lxml.etree");
270+
Symbol elementTreeSymbol = lxmlEtreeSymbols.get("ElementTree");
271+
assertThat(elementTreeSymbol.kind()).isEqualTo(Kind.CLASS);
272+
// FIXME: Original FQN is "xml.etree.ElementTree.ElementTree" and we should be able to retrieve it somehow
273+
assertThat(elementTreeSymbol.fullyQualifiedName()).isEqualTo("lxml.etree.ElementTree");
274+
}
275+
267276
@Test
268277
public void package_sqlite3_connect_type_in_ambiguous_symbol() {
269278
Map<String, Symbol> djangoSymbols = symbolsForModule("sqlite3");

python-frontend/typeshed_serializer/serializer/symbols.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
logger = logging.getLogger(__name__)
3434

3535
DEFAULT_EXPORTED_VARS = ["__name__", "__doc__", "__file__", "__package__"]
36+
SONAR_CUSTOM_BASE_CLASS = "SonarPythonAnalyzerFakeStub.CustomStubBase"
3637

3738

3839
class ParamKind(Enum):
@@ -337,7 +338,8 @@ def __init__(self, name: str, fullname: str, is_imported_module=False, type_desc
337338

338339
@classmethod
339340
def from_var(cls, var: mpn.Var, name: str = None):
340-
return cls(var.name if name is None else name, var.fullname, type_descriptor=TypeDescriptor(var.type) if var.type else None)
341+
return cls(var.name if name is None else name, var.fullname,
342+
type_descriptor=TypeDescriptor(var.type) if var.type else None)
341343

342344
def __eq__(self, other):
343345
return isinstance(other, VarSymbol) and self.to_proto() == other.to_proto()
@@ -361,6 +363,9 @@ def __init__(self, mypy_file: mpn.MypyFile):
361363
self.vars = []
362364
for key in mypy_file.names:
363365
name = mypy_file.names.get(key)
366+
if name.fullname == SONAR_CUSTOM_BASE_CLASS:
367+
# Ignore custom stub name
368+
continue
364369
symbol_table_node = name.node
365370
if isinstance(symbol_table_node, mpn.FuncDef):
366371
self.functions.append(FunctionSymbol(symbol_table_node, name=key))
@@ -528,10 +533,10 @@ def extract_return_type(func_def: mpn.FuncDef):
528533
return TypeDescriptor(func_type.ret_type)
529534

530535

531-
def save_module(ms: Union[ModuleSymbol, MergedModuleSymbol], is_debug=False, debug_dir="output", is_stdlib=True):
536+
def save_module(ms: Union[ModuleSymbol, MergedModuleSymbol], dir_name="stdlib_protobuf",
537+
is_debug=False, debug_dir="output"):
532538
ms_pb = ms.to_proto()
533-
save_dir = "../../src/main/resources/org/sonar/python/types/protobuf" if not is_debug else f"../{debug_dir}"
534-
save_dir = save_dir if is_stdlib else os.path.join(save_dir, "stubs")
539+
save_dir = f"../../src/main/resources/org/sonar/python/types/{dir_name}" if not is_debug else f"../{debug_dir}"
535540
save_string = ms_pb.SerializeToString() if not is_debug else str(ms_pb)
536541
open_mode = "wb" if not is_debug else "w"
537542
save_dir_path = os.path.join(CURRENT_PATH, save_dir)

python-frontend/typeshed_serializer/serializer/typeshed_serializer.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
STUBS_PATH = "../resources/typeshed/stubs"
2929
CURRENT_PATH = os.path.dirname(__file__)
3030
THIRD_PARTIES_STUBS = os.listdir(os.path.join(CURRENT_PATH, STUBS_PATH))
31+
CUSTOM_STUBS_PATH = "../resources/custom"
32+
SONAR_CUSTOM_BASE_STUB_MODULE = "SonarPythonAnalyzerFakeStub"
3133

3234

3335
def get_options(python_version=(3, 8)):
@@ -83,6 +85,12 @@ def walk_typeshed_third_parties(opt: options.Options = get_options()):
8385
return build_result, source_paths
8486

8587

88+
def walk_custom_stubs(opt: options.Options = get_options()):
89+
source_list, source_paths = get_sources(CUSTOM_STUBS_PATH, False)
90+
build_result = build.build(source_list, opt)
91+
return build_result, source_paths
92+
93+
8694
def get_sources(relative_path: str, generate_python2: bool):
8795
source_list = []
8896
source_paths = set()
@@ -118,7 +126,21 @@ def serialize_typeshed_stdlib(output_dir_name="output", python_version=(3, 8), i
118126
build_result, _ = walk_typeshed_stdlib(opt)
119127
for file in build_result.files:
120128
module_symbol = symbols.ModuleSymbol(build_result.files.get(file))
121-
symbols.save_module(module_symbol, is_debug=is_debug, debug_dir=output_dir_name)
129+
symbols.save_module(module_symbol, "stdlib_protobuf", is_debug=is_debug, debug_dir=output_dir_name)
130+
131+
132+
def serialize_custom_stubs(output_dir_name="output", python_version=(3, 8), is_debug=False):
133+
path = os.path.join(CURRENT_PATH, CUSTOM_STUBS_PATH)
134+
opt = get_options(python_version)
135+
build_result, _ = walk_custom_stubs(opt)
136+
for file in build_result.files:
137+
if file == SONAR_CUSTOM_BASE_STUB_MODULE:
138+
continue
139+
current_file = build_result.files.get(file)
140+
if not current_file.path.startswith(path):
141+
continue
142+
module_symbol = symbols.ModuleSymbol(current_file)
143+
symbols.save_module(module_symbol, "custom_protobuf", is_debug=is_debug, debug_dir=output_dir_name)
122144

123145

124146
def serialize_typeshed_stdlib_multiple_python_version():
@@ -129,14 +151,16 @@ def serialize_typeshed_stdlib_multiple_python_version():
129151

130152

131153
def save_merged_symbols(is_debug=False, is_third_parties=False):
154+
dir_name = "third_party_protobuf" if is_third_parties else "stdlib_protobuf"
132155
merged_modules = symbols_merger.merge_multiple_python_versions(is_third_parties)
133156
for mod in merged_modules:
134-
symbols.save_module(merged_modules[mod], is_debug=is_debug, debug_dir="output_merge", is_stdlib=not is_third_parties)
157+
symbols.save_module(merged_modules[mod], dir_name, is_debug=is_debug, debug_dir="output_merge")
135158

136159

137160
def main():
138161
save_merged_symbols()
139162
save_merged_symbols(is_third_parties=True)
163+
serialize_custom_stubs()
140164

141165

142166
if __name__ == '__main__':

python-frontend/typeshed_serializer/tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def typeshed_stdlib():
3333
return build_result
3434

3535

36+
@pytest.fixture(scope="session")
37+
def typeshed_custom_stubs():
38+
build_result, _ = typeshed_serializer.walk_custom_stubs()
39+
assert len(build_result.errors) == 0
40+
return build_result
41+
42+
3643
@pytest.fixture(scope="session")
3744
def fake_module_36_38():
3845
fake_module_path = os.path.join(os.path.dirname(__file__), "resources/fakemodule.pyi")

python-frontend/typeshed_serializer/tests/test_typeshed_serializer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ def test_serialize_typeshed_stdlib(typeshed_stdlib):
3636
assert symbols.save_module.call_count == len(typeshed_stdlib.files)
3737

3838

39+
def test_serialize_custom_stubs(typeshed_custom_stubs):
40+
typeshed_serializer.walk_custom_stubs = Mock(return_value=(typeshed_custom_stubs, set()))
41+
symbols.save_module = Mock()
42+
typeshed_serializer.serialize_custom_stubs()
43+
assert typeshed_serializer.walk_custom_stubs.call_count == 1
44+
# Not every files from "typeshed_custom_stubs" build are serialized, as some are builtins
45+
assert symbols.save_module.call_count == 50
46+
47+
3948
def test_all_third_parties_are_serialized(typeshed_third_parties):
4049
stub_modules = set()
4150
for stub_folder in typeshed_serializer.THIRD_PARTIES_STUBS:

0 commit comments

Comments
 (0)