Skip to content

Commit

Permalink
Improve how we determine if a symbol was imported from other libraries (
Browse files Browse the repository at this point in the history
  • Loading branch information
ccordoba12 authored Aug 21, 2021
1 parent 07c131f commit b5b2ff0
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 13 deletions.
49 changes: 36 additions & 13 deletions pylsp/plugins/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ def pylsp_document_symbols(config, document):
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements

symbols_settings = config.plugin_settings('jedi_symbols')
all_scopes = symbols_settings.get('all_scopes', True)
add_import_symbols = symbols_settings.get('include_import_symbols', True)
definitions = document.jedi_names(all_scopes=all_scopes)
symbols = []
exclude = set({})
redefinitions = {}

while definitions != []:
d = definitions.pop(0)

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

# Skip comparing module names.
# Skip imported symbols comparing module names.
sym_full_name = d.full_name
module_name = document.dot_path
document_dot_path = document.dot_path
if sym_full_name is not None:
# module_name returns where the symbol is imported, whereas
# full_name says where it really comes from. So if the parent
# modules in full_name are not in module_name, it means the
# symbol was not defined there.
# Note: The last element of sym_full_name is the symbol itself,
# so we don't need to use it below.
# We assume a symbol is imported from another module to start
# with.
imported_symbol = True
for mod in sym_full_name.split('.')[:-1]:
if mod in module_name:
imported_symbol = False

# The last element of sym_full_name is the symbol itself, so
# we need to discard it to do module comparisons below.
if '.' in sym_full_name:
sym_module_name = sym_full_name.rpartition('.')[0]

# This is necessary to display symbols in init files (the checks
# below fail without it).
if document_dot_path.endswith('__init__'):
document_dot_path = document_dot_path.rpartition('.')[0]

# document_dot_path is the module where the symbol is imported,
# whereas sym_module_name is the one where it was declared.
if sym_module_name.startswith(document_dot_path):
# If sym_module_name starts with the same string as document_dot_path,
# we can safely assume it was declared in the document.
imported_symbol = False
elif sym_module_name.split('.')[0] in document_dot_path.split('.'):
# If the first module in sym_module_name is one of the modules in
# document_dot_path, we need to check if sym_module_name starts
# with the modules in document_dot_path.
document_mods = document_dot_path.split('.')
for i in range(1, len(document_mods) + 1):
submod = '.'.join(document_mods[-i:])
if sym_module_name.startswith(submod):
imported_symbol = False
break

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

try:
Expand Down
2 changes: 2 additions & 0 deletions test/test_language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def test_exit_with_parent_process_died(client_exited_server): # pylint: disable
assert not client_exited_server.client_thread.is_alive()


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


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

0 comments on commit b5b2ff0

Please sign in to comment.