From 8c43511e6708c81c25bf67e76a4f03089811e38b Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 13:54:15 +0100 Subject: [PATCH 1/8] Update rope to 1.11.0 for multi-threading capabilities --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17569525..be764e4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ all = [ "pydocstyle>=6.3.0,<6.4.0", "pyflakes>=3.1.0,<3.2.0", "pylint>=2.5.0,<3.1", - "rope>1.2.0", + "rope>=1.11.0", "yapf>=0.33.0", "whatthepatch>=1.0.2,<2.0.0" ] @@ -45,7 +45,7 @@ pycodestyle = ["pycodestyle>=2.11.0,<2.12.0"] pydocstyle = ["pydocstyle>=6.3.0,<6.4.0"] pyflakes = ["pyflakes>=3.1.0,<3.2.0"] pylint = ["pylint>=2.5.0,<3.1"] -rope = ["rope>1.2.0"] +rope = ["rope>=1.11.0"] yapf = ["yapf>=0.33.0", "whatthepatch>=1.0.2,<2.0.0"] websockets = ["websockets>=10.3"] test = [ From bf7f53c357aa0a569a71f5cea5926f9175587870 Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 14:27:39 +0100 Subject: [PATCH 2/8] add unit test back --- pylsp/workspace.py | 22 ++--- test/plugins/test_autoimport.py | 160 ++++++++++++++++---------------- 2 files changed, 88 insertions(+), 94 deletions(-) diff --git a/pylsp/workspace.py b/pylsp/workspace.py index 5c6880c9..ed7ace6d 100644 --- a/pylsp/workspace.py +++ b/pylsp/workspace.py @@ -58,30 +58,20 @@ def __init__(self, root_uri, endpoint, config=None): # Whilst incubating, keep rope private self.__rope = None self.__rope_config = None - - # We have a sperate AutoImport object for each feature to avoid sqlite errors - # from accessing the same database from multiple threads. - # An upstream fix discussion is here: https://github.com/python-rope/rope/issues/713 - self.__rope_autoimport = ( - {} - ) # Type: Dict[Literal["completions", "code_actions"], rope.contrib.autoimport.sqlite.AutoImport] + self.__rope_autoimport = None def _rope_autoimport( self, rope_config: Optional, memory: bool = False, - feature: Literal["completions", "code_actions"] = "completions", ): # pylint: disable=import-outside-toplevel from rope.contrib.autoimport.sqlite import AutoImport - if feature not in ["completions", "code_actions"]: - raise ValueError(f"Unknown feature {feature}") - - if self.__rope_autoimport.get(feature, None) is None: + if self.__rope_autoimport is None: project = self._rope_project_builder(rope_config) - self.__rope_autoimport[feature] = AutoImport(project, memory=memory) - return self.__rope_autoimport[feature] + self.__rope_autoimport = AutoImport(project, memory=memory) + return self.__rope_autoimport def _rope_project_builder(self, rope_config): # pylint: disable=import-outside-toplevel @@ -388,8 +378,8 @@ def _create_cell_document( ) def close(self): - for _, autoimport in self.__rope_autoimport.items(): - autoimport.close() + if self.__rope_autoimport: + self.__rope_autoimport.close() class Document: diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index ec5c0a33..d9482571 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -1,13 +1,16 @@ # Copyright 2022- Python Language Server Contributors. from typing import Any, Dict, List -from unittest.mock import Mock +from unittest.mock import Mock, patch + +from test.test_notebook_document import wait_for_condition +from test.test_utils import send_initialize_request, send_notebook_did_open import jedi import parso import pytest -from pylsp import lsp, uris +from pylsp import IS_WIN, lsp, uris from pylsp.config.config import Config from pylsp.plugins.rope_autoimport import ( _get_score, @@ -251,79 +254,80 @@ def test_autoimport_code_actions_get_correct_module_name(autoimport_workspace, m # rope autoimport launches a sqlite database which checks from which thread it is called. # This makes the test below fail because we access the db from a different thread. # See https://stackoverflow.com/questions/48218065/objects-created-in-a-thread-can-only-be-used-in-that-same-thread -# @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") -# def test_autoimport_completions_for_notebook_document( -# client_server_pair, -# ): -# client, server = client_server_pair -# send_initialize_request(client) - -# with patch.object(server._endpoint, "notify") as mock_notify: -# # Expectations: -# # 1. We receive an autoimport suggestion for "os" in the first cell because -# # os is imported after that. -# # 2. We don't receive an autoimport suggestion for "os" in the second cell because it's -# # already imported in the second cell. -# # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's -# # already imported in the second cell. -# # 4. We receive an autoimport suggestion for "sys" because it's not already imported -# send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"]) -# wait_for_condition(lambda: mock_notify.call_count >= 3) - -# server.m_workspace__did_change_configuration( -# settings={ -# "pylsp": { -# "plugins": { -# "rope_autoimport": { -# "memory": True, -# "completions": {"enabled": True}, -# }, -# } -# } -# } -# ) -# rope_autoimport_settings = server.workspace._config.plugin_settings( -# "rope_autoimport" -# ) -# assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True -# assert rope_autoimport_settings.get("memory", False) is True - -# # 1. -# suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get( -# "items" -# ) -# assert any( -# suggestion -# for suggestion in suggestions -# if contains_autoimport(suggestion, "os") -# ) - -# # 2. -# suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( -# "items" -# ) -# assert not any( -# suggestion -# for suggestion in suggestions -# if contains_autoimport(suggestion, "os") -# ) - -# # 3. -# suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( -# "items" -# ) -# assert not any( -# suggestion -# for suggestion in suggestions -# if contains_autoimport(suggestion, "os") -# ) - -# # 4. -# suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( -# "items" -# ) -# assert any( -# suggestion -# for suggestion in suggestions -# if contains_autoimport(suggestion, "sys") -# ) +@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") +def test_autoimport_completions_for_notebook_document( + client_server_pair, +): + client, server = client_server_pair + send_initialize_request( + client, + { + "pylsp": { + "plugins": { + "rope_autoimport": { + "memory": True, + "enabled": True, + "completions": {"enabled": True}, + }, + } + } + }, + ) + + with patch.object(server._endpoint, "notify") as mock_notify: + # Expectations: + # 1. We receive an autoimport suggestion for "os" in the first cell because + # os is imported after that. + # 2. We don't receive an autoimport suggestion for "os" in the second cell because it's + # already imported in the second cell. + # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's + # already imported in the second cell. + # 4. We receive an autoimport suggestion for "sys" because it's not already imported + send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"]) + wait_for_condition(lambda: mock_notify.call_count >= 3) + + rope_autoimport_settings = server.workspace._config.plugin_settings( + "rope_autoimport" + ) + assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True + assert rope_autoimport_settings.get("memory", False) is True + + # 1. + suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get( + "items" + ) + assert any( + suggestion + for suggestion in suggestions + if contains_autoimport(suggestion, "os") + ) + + # 2. + suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( + "items" + ) + assert not any( + suggestion + for suggestion in suggestions + if contains_autoimport(suggestion, "os") + ) + + # 3. + suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( + "items" + ) + assert not any( + suggestion + for suggestion in suggestions + if contains_autoimport(suggestion, "os") + ) + + # 4. + suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( + "items" + ) + assert any( + suggestion + for suggestion in suggestions + if contains_autoimport(suggestion, "sys") + ) From fbad9282d8669830673ba24906ff0e833680f2e3 Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 15:29:07 +0100 Subject: [PATCH 3/8] extend unit test --- pylsp/plugins/rope_autoimport.py | 2 +- test/plugins/test_autoimport.py | 80 +++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/pylsp/plugins/rope_autoimport.py b/pylsp/plugins/rope_autoimport.py index ca3db1cf..ebcc7070 100644 --- a/pylsp/plugins/rope_autoimport.py +++ b/pylsp/plugins/rope_autoimport.py @@ -295,7 +295,7 @@ def pylsp_code_actions( word = get_name_or_module(document, diagnostic) log.debug(f"autoimport: searching for word: {word}") rope_config = config.settings(document_path=document.path).get("rope", {}) - autoimport = workspace._rope_autoimport(rope_config, feature="code_actions") + autoimport = workspace._rope_autoimport(rope_config) suggestions = list(autoimport.search_full(word)) log.debug("autoimport: suggestions: %s", suggestions) results = list( diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index d9482571..6ae45641 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -28,12 +28,16 @@ DOC_URI = uris.from_fs_path(__file__) -def contains_autoimport(suggestion: Dict[str, Any], module: str) -> bool: +def contains_autoimport_completion(suggestion: Dict[str, Any], module: str) -> bool: """Checks if `suggestion` contains an autoimport for `module`.""" return suggestion.get("label", "") == module and "import" in suggestion.get( "detail", "" ) +def contains_autoimport_quickfix(suggestion: Dict[str, Any], module: str) -> bool: + """Checks if `suggestion` contains an autoimport for `module`.""" + return suggestion.get("title", "") == f"import {module}" + @pytest.fixture(scope="session") def autoimport_workspace(tmp_path_factory) -> Workspace: @@ -251,11 +255,21 @@ def test_autoimport_code_actions_get_correct_module_name(autoimport_workspace, m assert module_name == "os" -# rope autoimport launches a sqlite database which checks from which thread it is called. -# This makes the test below fail because we access the db from a different thread. -# See https://stackoverflow.com/questions/48218065/objects-created-in-a-thread-can-only-be-used-in-that-same-thread +def make_context(module_name, line, character_start, character_end): + return { + "diagnostics": [ + { + "message": f"undefined name '{module_name}'", + "range": { + "start": {"line": line, "character": character_start}, + "end": {"line": line, "character": character_end} + } + } + ] + } + @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") -def test_autoimport_completions_for_notebook_document( +def test_autoimport_code_actions_and_completions_for_notebook_document( client_server_pair, ): client, server = client_server_pair @@ -293,41 +307,61 @@ def test_autoimport_completions_for_notebook_document( assert rope_autoimport_settings.get("memory", False) is True # 1. - suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get( - "items" - ) + context = make_context("os", 0, 0, 2) + quickfix_suggestions = server.code_actions("cell_1_uri", {}, context) assert any( suggestion - for suggestion in suggestions - if contains_autoimport(suggestion, "os") - ) + for suggestion in quickfix_suggestions + if contains_autoimport_quickfix(suggestion, "os") + ), "Can't find os quickfix suggestion in cell_1_uri" + completion_suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get("items") + assert any( + suggestion + for suggestion in completion_suggestions + if contains_autoimport_completion(suggestion, "os") + ), "Can't find os completion suggestion in cell_1_uri" # 2. - suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( + # We don't test code actions here as in this case, there would be no code actions sent bc + # there wouldn't be a diagnostics message. + completion_suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( "items" ) assert not any( suggestion - for suggestion in suggestions - if contains_autoimport(suggestion, "os") - ) + for suggestion in completion_suggestions + if contains_autoimport_completion(suggestion, "os") + ), "Found os completion suggestion in cell_2_uri" # 3. - suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( + # Same as in 2. + completion_suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( "items" ) assert not any( suggestion - for suggestion in suggestions - if contains_autoimport(suggestion, "os") - ) + for suggestion in completion_suggestions + if contains_autoimport_completion(suggestion, "os") + ), "Found os completion suggestion in cell_3_uri" # 4. - suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( + context = make_context("sys", 0, 0, 3) + quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) + assert any( + suggestion + for suggestion in quickfix_suggestions + if contains_autoimport_quickfix(suggestion, "sys") + ), "Can't find 'sys' quickfix suggestion in cell_4_uri" + completion_suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( "items" ) assert any( suggestion - for suggestion in suggestions - if contains_autoimport(suggestion, "sys") - ) + for suggestion in completion_suggestions + if contains_autoimport_completion(suggestion, "sys") + ), "Can't find 'sys' completion suggestion in cell_4_uri" + + # 5. if context doesn't contain message with "undefined name ...", we send empty suggestions + context = {"diagnostics": [{ "message": "A random message"}]} + quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) + assert len(quickfix_suggestions) == 0 \ No newline at end of file From 33b16dabd04fa24f7e8f212d9a00005ce6c4a33b Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 15:37:23 +0100 Subject: [PATCH 4/8] adjust unit tests --- test/plugins/test_autoimport.py | 68 +++++++++++++++------------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index 6ae45641..772c3a26 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -29,13 +29,14 @@ def contains_autoimport_completion(suggestion: Dict[str, Any], module: str) -> bool: - """Checks if `suggestion` contains an autoimport for `module`.""" + """Checks if `suggestion` contains an autoimport completion for `module`.""" return suggestion.get("label", "") == module and "import" in suggestion.get( "detail", "" ) + def contains_autoimport_quickfix(suggestion: Dict[str, Any], module: str) -> bool: - """Checks if `suggestion` contains an autoimport for `module`.""" + """Checks if `suggestion` contains an autoimport quick fix for `module`.""" return suggestion.get("title", "") == f"import {module}" @@ -262,12 +263,13 @@ def make_context(module_name, line, character_start, character_end): "message": f"undefined name '{module_name}'", "range": { "start": {"line": line, "character": character_start}, - "end": {"line": line, "character": character_end} - } + "end": {"line": line, "character": character_end}, + }, } ] } + @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") def test_autoimport_code_actions_and_completions_for_notebook_document( client_server_pair, @@ -310,58 +312,48 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( context = make_context("os", 0, 0, 2) quickfix_suggestions = server.code_actions("cell_1_uri", {}, context) assert any( - suggestion - for suggestion in quickfix_suggestions - if contains_autoimport_quickfix(suggestion, "os") - ), "Can't find os quickfix suggestion in cell_1_uri" - completion_suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get("items") + s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "os") + ), "Can't find 'os' quickfix suggestion in cell_1_uri" + completion_suggestions = server.completions( + "cell_1_uri", {"line": 0, "character": 2} + ).get("items") assert any( - suggestion - for suggestion in completion_suggestions - if contains_autoimport_completion(suggestion, "os") - ), "Can't find os completion suggestion in cell_1_uri" + s for s in completion_suggestions if contains_autoimport_completion(s, "os") + ), "Can't find 'os' completion suggestion in cell_1_uri" # 2. # We don't test code actions here as in this case, there would be no code actions sent bc # there wouldn't be a diagnostics message. - completion_suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( - "items" - ) + completion_suggestions = server.completions( + "cell_2_uri", {"line": 1, "character": 2} + ).get("items") assert not any( - suggestion - for suggestion in completion_suggestions - if contains_autoimport_completion(suggestion, "os") - ), "Found os completion suggestion in cell_2_uri" + s for s in completion_suggestions if contains_autoimport_completion(s, "os") + ), "Found 'os' completion suggestion in cell_2_uri" # 3. # Same as in 2. - completion_suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( - "items" - ) + completion_suggestions = server.completions( + "cell_3_uri", {"line": 0, "character": 2} + ).get("items") assert not any( - suggestion - for suggestion in completion_suggestions - if contains_autoimport_completion(suggestion, "os") - ), "Found os completion suggestion in cell_3_uri" + s for s in completion_suggestions if contains_autoimport_completion(s, "os") + ), "Found 'os' completion suggestion in cell_3_uri" # 4. context = make_context("sys", 0, 0, 3) quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) assert any( - suggestion - for suggestion in quickfix_suggestions - if contains_autoimport_quickfix(suggestion, "sys") + s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "sys") ), "Can't find 'sys' quickfix suggestion in cell_4_uri" - completion_suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( - "items" - ) + completion_suggestions = server.completions( + "cell_4_uri", {"line": 0, "character": 3} + ).get("items") assert any( - suggestion - for suggestion in completion_suggestions - if contains_autoimport_completion(suggestion, "sys") + s for s in completion_suggestions if contains_autoimport_completion(s, "sys") ), "Can't find 'sys' completion suggestion in cell_4_uri" # 5. if context doesn't contain message with "undefined name ...", we send empty suggestions - context = {"diagnostics": [{ "message": "A random message"}]} + context = {"diagnostics": [{"message": "A random message"}]} quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) - assert len(quickfix_suggestions) == 0 \ No newline at end of file + assert len(quickfix_suggestions) == 0 From 017a0cdea057e6f71bd25f65ab03a5bd978c2d87 Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 15:40:16 +0100 Subject: [PATCH 5/8] wip --- test/plugins/test_autoimport.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index 772c3a26..a7806063 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -309,8 +309,9 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( assert rope_autoimport_settings.get("memory", False) is True # 1. - context = make_context("os", 0, 0, 2) - quickfix_suggestions = server.code_actions("cell_1_uri", {}, context) + quickfix_suggestions = server.code_actions( + "cell_1_uri", {}, make_context("os", 0, 0, 2) + ) assert any( s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "os") ), "Can't find 'os' quickfix suggestion in cell_1_uri" @@ -341,8 +342,9 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( ), "Found 'os' completion suggestion in cell_3_uri" # 4. - context = make_context("sys", 0, 0, 3) - quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) + quickfix_suggestions = server.code_actions( + "cell_4_uri", {}, make_context("sys", 0, 0, 3) + ) assert any( s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "sys") ), "Can't find 'sys' quickfix suggestion in cell_4_uri" From 428c9cdd64e67af7adabda76946524d568ebde56 Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 15:45:09 +0100 Subject: [PATCH 6/8] make unit test easier to read --- test/plugins/test_autoimport.py | 58 ++++++++++++--------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index a7806063..d0a4ad44 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -270,6 +270,10 @@ def make_context(module_name, line, character_start, character_end): } +def position(line, character): + return {"line": line, "character": character} + + @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") def test_autoimport_code_actions_and_completions_for_notebook_document( client_server_pair, @@ -309,53 +313,31 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( assert rope_autoimport_settings.get("memory", False) is True # 1. - quickfix_suggestions = server.code_actions( - "cell_1_uri", {}, make_context("os", 0, 0, 2) - ) - assert any( - s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "os") - ), "Can't find 'os' quickfix suggestion in cell_1_uri" - completion_suggestions = server.completions( - "cell_1_uri", {"line": 0, "character": 2} - ).get("items") - assert any( - s for s in completion_suggestions if contains_autoimport_completion(s, "os") - ), "Can't find 'os' completion suggestion in cell_1_uri" + quick_fixes = server.code_actions("cell_1_uri", {}, make_context("os", 0, 0, 2)) + assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "os")) + + completions = server.completions("cell_1_uri", position(0, 2)).get("items") + assert any(s for s in completions if contains_autoimport_completion(s, "os")) # 2. # We don't test code actions here as in this case, there would be no code actions sent bc # there wouldn't be a diagnostics message. - completion_suggestions = server.completions( - "cell_2_uri", {"line": 1, "character": 2} - ).get("items") - assert not any( - s for s in completion_suggestions if contains_autoimport_completion(s, "os") - ), "Found 'os' completion suggestion in cell_2_uri" + completions = server.completions("cell_2_uri", position(1, 2)).get("items") + assert not any(s for s in completions if contains_autoimport_completion(s, "os")) # 3. # Same as in 2. - completion_suggestions = server.completions( - "cell_3_uri", {"line": 0, "character": 2} - ).get("items") - assert not any( - s for s in completion_suggestions if contains_autoimport_completion(s, "os") - ), "Found 'os' completion suggestion in cell_3_uri" + completions = server.completions("cell_3_uri", position(0, 2)).get("items") + assert not any(s for s in completions if contains_autoimport_completion(s, "os")) # 4. - quickfix_suggestions = server.code_actions( - "cell_4_uri", {}, make_context("sys", 0, 0, 3) - ) - assert any( - s for s in quickfix_suggestions if contains_autoimport_quickfix(s, "sys") - ), "Can't find 'sys' quickfix suggestion in cell_4_uri" - completion_suggestions = server.completions( - "cell_4_uri", {"line": 0, "character": 3} - ).get("items") - assert any( - s for s in completion_suggestions if contains_autoimport_completion(s, "sys") - ), "Can't find 'sys' completion suggestion in cell_4_uri" + quick_fixes = server.code_actions("cell_4_uri", {}, make_context("sys", 0, 0, 3)) + assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "sys")) + + completions = server.completions("cell_4_uri", position(0, 3)).get("items") + assert any(s for s in completions if contains_autoimport_completion(s, "sys")) # 5. if context doesn't contain message with "undefined name ...", we send empty suggestions context = {"diagnostics": [{"message": "A random message"}]} - quickfix_suggestions = server.code_actions("cell_4_uri", {}, context) - assert len(quickfix_suggestions) == 0 + quick_fixes = server.code_actions("cell_4_uri", {}, context) + assert len(quick_fixes) == 0 From 5804651cb6b969776ab2932f468cee848b3e701a Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 15:47:05 +0100 Subject: [PATCH 7/8] add 5. expectation to unit test --- test/plugins/test_autoimport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/plugins/test_autoimport.py b/test/plugins/test_autoimport.py index d0a4ad44..9f02965b 100644 --- a/test/plugins/test_autoimport.py +++ b/test/plugins/test_autoimport.py @@ -302,7 +302,8 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( # already imported in the second cell. # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's # already imported in the second cell. - # 4. We receive an autoimport suggestion for "sys" because it's not already imported + # 4. We receive an autoimport suggestion for "sys" because it's not already imported. + # 5. If diagnostics doesn't contain "undefined name ...", we send empty quick fix suggestions. send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"]) wait_for_condition(lambda: mock_notify.call_count >= 3) @@ -337,7 +338,7 @@ def test_autoimport_code_actions_and_completions_for_notebook_document( completions = server.completions("cell_4_uri", position(0, 3)).get("items") assert any(s for s in completions if contains_autoimport_completion(s, "sys")) - # 5. if context doesn't contain message with "undefined name ...", we send empty suggestions + # 5. context = {"diagnostics": [{"message": "A random message"}]} quick_fixes = server.code_actions("cell_4_uri", {}, context) assert len(quick_fixes) == 0 From 9660f34f2dc79572ff98683464229c1d0352a562 Mon Sep 17 00:00:00 2001 From: tkrabel Date: Mon, 18 Dec 2023 17:11:57 +0100 Subject: [PATCH 8/8] remove unused import --- pylsp/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylsp/workspace.py b/pylsp/workspace.py index ed7ace6d..5e8e548f 100644 --- a/pylsp/workspace.py +++ b/pylsp/workspace.py @@ -8,7 +8,7 @@ import re import uuid import functools -from typing import Literal, Optional, Generator, Callable, List +from typing import Optional, Generator, Callable, List from threading import RLock import jedi