Skip to content

Commit b5b2ff0

Browse files
authored
Improve how we determine if a symbol was imported from other libraries (#71)
1 parent 07c131f commit b5b2ff0

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
lines changed

pylsp/plugins/symbols.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ def pylsp_document_symbols(config, document):
1616
# pylint: disable=too-many-nested-blocks
1717
# pylint: disable=too-many-locals
1818
# pylint: disable=too-many-branches
19+
# pylint: disable=too-many-statements
20+
1921
symbols_settings = config.plugin_settings('jedi_symbols')
2022
all_scopes = symbols_settings.get('all_scopes', True)
2123
add_import_symbols = symbols_settings.get('include_import_symbols', True)
2224
definitions = document.jedi_names(all_scopes=all_scopes)
2325
symbols = []
2426
exclude = set({})
2527
redefinitions = {}
28+
2629
while definitions != []:
2730
d = definitions.pop(0)
2831

@@ -33,27 +36,47 @@ def pylsp_document_symbols(config, document):
3336
if ' import ' in code or 'import ' in code:
3437
continue
3538

36-
# Skip comparing module names.
39+
# Skip imported symbols comparing module names.
3740
sym_full_name = d.full_name
38-
module_name = document.dot_path
41+
document_dot_path = document.dot_path
3942
if sym_full_name is not None:
40-
# module_name returns where the symbol is imported, whereas
41-
# full_name says where it really comes from. So if the parent
42-
# modules in full_name are not in module_name, it means the
43-
# symbol was not defined there.
44-
# Note: The last element of sym_full_name is the symbol itself,
45-
# so we don't need to use it below.
43+
# We assume a symbol is imported from another module to start
44+
# with.
4645
imported_symbol = True
47-
for mod in sym_full_name.split('.')[:-1]:
48-
if mod in module_name:
49-
imported_symbol = False
46+
47+
# The last element of sym_full_name is the symbol itself, so
48+
# we need to discard it to do module comparisons below.
49+
if '.' in sym_full_name:
50+
sym_module_name = sym_full_name.rpartition('.')[0]
51+
52+
# This is necessary to display symbols in init files (the checks
53+
# below fail without it).
54+
if document_dot_path.endswith('__init__'):
55+
document_dot_path = document_dot_path.rpartition('.')[0]
56+
57+
# document_dot_path is the module where the symbol is imported,
58+
# whereas sym_module_name is the one where it was declared.
59+
if sym_module_name.startswith(document_dot_path):
60+
# If sym_module_name starts with the same string as document_dot_path,
61+
# we can safely assume it was declared in the document.
62+
imported_symbol = False
63+
elif sym_module_name.split('.')[0] in document_dot_path.split('.'):
64+
# If the first module in sym_module_name is one of the modules in
65+
# document_dot_path, we need to check if sym_module_name starts
66+
# with the modules in document_dot_path.
67+
document_mods = document_dot_path.split('.')
68+
for i in range(1, len(document_mods) + 1):
69+
submod = '.'.join(document_mods[-i:])
70+
if sym_module_name.startswith(submod):
71+
imported_symbol = False
72+
break
5073

5174
# When there's no __init__.py next to a file or in one of its
52-
# parents, the check above fails. However, Jedi has a nice way
75+
# parents, the checks above fail. However, Jedi has a nice way
5376
# to tell if the symbol was declared in the same file: if
5477
# full_name starts by __main__.
5578
if imported_symbol:
56-
if not sym_full_name.startswith('__main__'):
79+
if not sym_module_name.startswith('__main__'):
5780
continue
5881

5982
try:

test/test_language_server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def test_exit_with_parent_process_died(client_exited_server): # pylint: disable
102102
assert not client_exited_server.client_thread.is_alive()
103103

104104

105+
@flaky(max_runs=10, min_passes=1)
105106
@pytest.mark.skipif(sys.platform.startswith('linux'), reason='Fails on linux')
106107
def test_not_exit_without_check_parent_process_flag(client_server): # pylint: disable=redefined-outer-name
107108
response = client_server._endpoint.request('initialize', {
@@ -112,6 +113,7 @@ def test_not_exit_without_check_parent_process_flag(client_server): # pylint: d
112113
assert 'capabilities' in response
113114

114115

116+
@flaky(max_runs=10, min_passes=1)
115117
@pytest.mark.skipif(RUNNING_IN_CI, reason='This test is hanging on CI')
116118
def test_missing_message(client_server): # pylint: disable=redefined-outer-name
117119
with pytest.raises(JsonRpcMethodNotFound):

0 commit comments

Comments
 (0)