def test_lsp_rename_variable_at_line_start(): """Tests renaming a variable that appears at the start of a line.""" with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri((REFACTOR_TEST_ROOT / "rename_test2.py")) actual = ls_session.text_document_rename( { "textDocument": {"uri": uri}, "position": {"line": 1, "character": 0}, "newName": "y", } ) expected = { "documentChanges": [ { "textDocument": { "uri": uri, "version": 0, }, "edits": [ { "range": { "start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 1}, }, "newText": "y", }, ], } ], } assert_that(actual, is_(expected))
def test_lsp_code_action2() -> None: """Tests edge case for code actions. Identified in: https://github.com/pappasam/jedi-language-server/issues/96 """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri((REFACTOR_TEST_ROOT / "code_action_test2.py")) actual = ls_session.text_document_code_action({ "textDocument": { "uri": uri }, "range": { "start": { "line": 2, "character": 6 }, "end": { "line": 2, "character": 6 }, }, "context": { "diagnostics": [] }, }) assert_that(actual, is_(None))
def test_hover_on_method_no_docstring(): """Tests hover on the name of a class method without doc string. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover({ "textDocument": { "uri": uri }, "position": { "line": 10, "character": 6 }, }) expected = { "contents": { "kind": "markdown", "value": "```\nsome_method2()\n```\n", }, "range": { "start": { "line": 10, "character": 2 }, "end": { "line": 10, "character": 14 }, }, } assert_that(actual, is_(expected))
def test_hover_on_module(): """Tests hover on the name of a imported module. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover({ "textDocument": { "uri": uri }, "position": { "line": 2, "character": 12 }, }) expected = { "contents": { "kind": "markdown", "value": "```\nModule doc string for testing.\n```\n", }, "range": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 17 }, }, } assert_that(actual, is_(expected))
def test_lsp_rename_last_line(): """Tests whether rename works for end of file edge case. This example was receiving a KeyError, but now we check for end-1 to fit within correct range. """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri((REFACTOR_TEST_ROOT / "rename_test3.py")) actual = ls_session.text_document_rename( { "textDocument": {"uri": uri}, "position": {"line": 14, "character": 7}, "newName": "args2", } ) expected = { "documentChanges": [ { "textDocument": { "uri": uri, "version": 0, }, "edits": [ { "range": { "start": {"line": 11, "character": 4}, "end": {"line": 11, "character": 4}, }, "newText": "2", }, { "range": { "start": {"line": 12, "character": 7}, "end": {"line": 12, "character": 7}, }, "newText": "2", }, { "range": { "start": {"line": 12, "character": 15}, "end": {"line": 12, "character": 15}, }, "newText": "2", }, { "range": { "start": {"line": 14, "character": 10}, "end": {"line": 14, "character": 12}, }, "newText": "2)\n", }, ], } ], } assert_that(actual, is_(expected))
def test_lsp_completion() -> None: """Test a simple completion request. Test Data: tests/test_data/completion/completion_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(COMPLETION_TEST_ROOT / "completion_test1.py") actual = ls_session.text_document_completion({ "textDocument": { "uri": uri }, "position": { "line": 8, "character": 2 }, "context": { "triggerKind": 1 }, }) expected = { "isIncomplete": False, "items": [{ "label": "my_function", "kind": 3, "sortText": "z", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, }], } assert_that(actual, is_(expected)) actual = ls_session.completion_item_resolve({ "label": "my_function", "kind": 3, "sortText": "z", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, }) expected = { "label": "my_function", "kind": 3, "detail": "def my_function", "documentation": { "kind": "markdown", "value": "```\nmy_function()\n\nSimple test function.\n```\n", }, "sortText": "z", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, } assert_that(actual, is_(expected))
def test_rename_package() -> None: """Tests renaming of an imported package.""" test_root = REFACTOR_TEST_ROOT / "rename_package_test1" with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(test_root / "rename_test_main.py") actual = ls_session.text_document_rename( { "textDocument": {"uri": uri}, "position": {"line": 2, "character": 12}, "newName": "new_name", } ) old_name_uri = as_uri(test_root / "old_name") new_name_uri = as_uri(test_root / "new_name") expected = { "documentChanges": [ { "textDocument": { "uri": uri, "version": 0, }, "edits": [ { "range": { "start": {"line": 2, "character": 5}, "end": {"line": 2, "character": 8}, }, "newText": "new", } ], }, { "kind": "rename", "oldUri": old_name_uri, "newUri": new_name_uri, "options": {"overwrite": True, "ignoreIfExists": True}, }, ] } assert_that(actual, is_(expected))
def test_eager_lsp_completion() -> None: """Test a simple completion request, with eager resolution. Test Data: tests/test_data/completion/completion_test1.py """ with session.LspSession() as ls_session: # Initialize, asking for eager resolution. initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["initializationOptions"] = { "completion": { "resolveEagerly": True } } ls_session.initialize(initialize_params) uri = as_uri(COMPLETION_TEST_ROOT / "completion_test1.py") actual = ls_session.text_document_completion({ "textDocument": { "uri": uri }, "position": { "line": 8, "character": 2 }, "context": { "triggerKind": 1 }, }) # pylint: disable=line-too-long expected = { "isIncomplete": False, "items": [{ "label": "my_function", "kind": 3, "detail": "def my_function", "documentation": { "kind": "markdown", "value": "```\nmy_function()\n\nSimple test function.\n```\n", }, "sortText": "z", "filterText": "my_function", "insertText": "my_function()$0", "insertTextFormat": 2, }], } assert_that(actual, is_(expected))
def test_lsp_completion_class_method() -> None: """Checks whether completion returns self unnecessarily. References: https://github.com/pappasam/jedi-language-server/issues/121 Note: I resolve eagerly to make test simpler """ with session.LspSession() as ls_session: # Initialize, asking for eager resolution. initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["initializationOptions"] = { "completion": { "resolveEagerly": True } } ls_session.initialize(initialize_params) uri = as_uri(COMPLETION_TEST_ROOT / "completion_test_class_self.py") actual = ls_session.text_document_completion({ "textDocument": { "uri": uri }, "position": { "line": 7, "character": 13 }, "context": { "triggerKind": 1 }, }) # pylint: disable=line-too-long expected = { "isIncomplete": False, "items": [{ "label": "some_method", "kind": 3, "detail": "def some_method", "documentation": { "kind": "markdown", "value": "```\nsome_method(x)\n\nGreat method.\n```\n", }, "sortText": "z", "filterText": "some_method", "insertText": "some_method(${1:x})$0", "insertTextFormat": 2, }], } assert_that(actual, is_(expected))
def test_highlighting(position, expected): """Tests highlighting on import statement. Test Data: tests/test_data/highlighting/highlighting_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HIGHLIGHTING_TEST_ROOT / "highlighting_test1.py") actual = ls_session.text_document_highlight({ "textDocument": { "uri": uri }, "position": position, }) assert_that(actual, is_(expected))
def test_signature_help(trigger_char, column, active_param): """Tests signature help response for a function. Test Data: tests/test_data/signature/signature_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(SIGNATURE_TEST_ROOT / "signature_test1.py") actual = ls_session.text_document_signature_help( { "textDocument": {"uri": uri}, "position": {"line": 7, "character": column}, "context": { "isRetrigger": False, "triggerCharacter": trigger_char, "triggerKind": 2, }, } ) expected = { "signatures": [ { "label": "some_function(arg1: str, arg2: int, arg3: list)", "documentation": { "kind": "markdown", "value": "```\nThis is a test function.\n```\n", }, "parameters": [ {"label": "arg1: str"}, {"label": "arg2: int"}, {"label": "arg3: list"}, ], } ], "activeSignature": 0, "activeParameter": active_param, } assert_that(actual, is_(expected))
def test_hover_on_class(): """Tests hover on the name of a class. Test Data: tests/test_data/hover/hover_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(HOVER_TEST_ROOT / "hover_test1.py") actual = ls_session.text_document_hover({ "textDocument": { "uri": uri }, "position": { "line": 6, "character": 21 }, }) expected = { "contents": { "kind": "markdown", "value": "```python\nclass SomeClass()\n```\n---\n```text\nClass doc string for testing.\n```\n**Full name:** `somemodule.SomeClass`", }, "range": { "start": { "line": 6, "character": 15 }, "end": { "line": 6, "character": 24 }, }, } assert_that(actual, is_(expected))
"""Tests for references requests.""" import copy import pytest from hamcrest import assert_that, is_ from tests import TEST_DATA from tests.lsp_test_client import session from tests.lsp_test_client.defaults import VSCODE_DEFAULT_INITIALIZE from tests.lsp_test_client.utils import as_uri REFERENCES_TEST_ROOT = TEST_DATA / "references" references1 = as_uri(REFERENCES_TEST_ROOT / "references_test1.py") @pytest.mark.parametrize( ["position", "expected"], [ ({ "line": 2, "character": 3 }, None), ( { "line": 4, "character": 8 }, [ {
def test_document_symbol_no_hierarchy() -> None: """Test document symbol request with hierarchy turned off. Test Data: tests/test_data/symbol/symbol_test1.py """ initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["capabilities"]["textDocument"]["documentSymbol"][ "hierarchicalDocumentSymbolSupport"] = False with session.LspSession() as ls_session: ls_session.initialize(initialize_params) uri = as_uri(SYMBOL_TEST_ROOT / "symbol_test1.py") actual = ls_session.text_document_symbol( {"textDocument": { "uri": uri }}) expected = [ { "name": "Any", "kind": 5, "location": { "uri": uri, "range": { "start": { "line": 2, "character": 19 }, "end": { "line": 2, "character": 22 }, }, }, "containerName": "typing.Any", }, { "name": "somemodule", "kind": 2, "location": { "uri": uri, "range": { "start": { "line": 4, "character": 14 }, "end": { "line": 4, "character": 24 }, }, }, "containerName": "somemodule", }, { "name": "somemodule2", "kind": 2, "location": { "uri": uri, "range": { "start": { "line": 4, "character": 26 }, "end": { "line": 4, "character": 37 }, }, }, "containerName": "somemodule2", }, { "name": "SOME_CONSTANT", "kind": 13, "location": { "uri": uri, "range": { "start": { "line": 6, "character": 0 }, "end": { "line": 6, "character": 13 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SOME_CONSTANT", }, { "name": "do_work", "kind": 12, "location": { "uri": uri, "range": { "start": { "line": 9, "character": 4 }, "end": { "line": 9, "character": 11 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.do_work", }, { "name": "SomeClass", "kind": 5, "location": { "uri": uri, "range": { "start": { "line": 15, "character": 6 }, "end": { "line": 15, "character": 15 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SomeClass", }, { "name": "__init__", "kind": 12, "location": { "uri": uri, "range": { "start": { "line": 18, "character": 8 }, "end": { "line": 18, "character": 16 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SomeClass.__init__", }, { "name": "somedata", "kind": 13, "location": { "uri": uri, "range": { "start": { "line": 19, "character": 13 }, "end": { "line": 19, "character": 21 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SomeClass.__init__.somedata", }, { "name": "do_something", "kind": 12, "location": { "uri": uri, "range": { "start": { "line": 21, "character": 8 }, "end": { "line": 21, "character": 20 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SomeClass.do_something", }, { "name": "so_something_else", "kind": 12, "location": { "uri": uri, "range": { "start": { "line": 24, "character": 8 }, "end": { "line": 24, "character": 25 }, }, }, "containerName": "tests.test_data.symbol.symbol_test1.SomeClass.so_something_else", }, ] assert_that(actual, is_(expected))
def test_document_symbol() -> None: """Test document symbol request. Test Data: tests/test_data/symbol/symbol_test1.py """ with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri(SYMBOL_TEST_ROOT / "symbol_test1.py") actual = ls_session.text_document_symbol( {"textDocument": { "uri": uri }}) expected = [ { "name": "Any", "kind": 5, "range": { "start": { "line": 2, "character": 0 }, "end": { "line": 2, "character": 22 }, }, "selectionRange": { "start": { "line": 2, "character": 19 }, "end": { "line": 2, "character": 22 }, }, "detail": "class Any", "children": [], }, { "name": "somemodule", "kind": 2, "range": { "start": { "line": 4, "character": 0 }, "end": { "line": 4, "character": 37 }, }, "selectionRange": { "start": { "line": 4, "character": 14 }, "end": { "line": 4, "character": 24 }, }, "detail": "module somemodule", "children": [], }, { "name": "somemodule2", "kind": 2, "range": { "start": { "line": 4, "character": 0 }, "end": { "line": 4, "character": 37 }, }, "selectionRange": { "start": { "line": 4, "character": 26 }, "end": { "line": 4, "character": 37 }, }, "detail": "module somemodule2", "children": [], }, { "name": "SOME_CONSTANT", "kind": 13, "range": { "start": { "line": 6, "character": 0 }, "end": { "line": 6, "character": 17 }, }, "selectionRange": { "start": { "line": 6, "character": 0 }, "end": { "line": 6, "character": 13 }, }, "detail": "SOME_CONSTANT = 1", "children": [], }, { "name": "do_work", "kind": 12, "range": { "start": { "line": 9, "character": 0 }, "end": { "line": 12, "character": 35 }, }, "selectionRange": { "start": { "line": 9, "character": 4 }, "end": { "line": 9, "character": 11 }, }, "detail": "def do_work", "children": [], }, { "name": "SomeClass", "kind": 5, "range": { "start": { "line": 15, "character": 0 }, "end": { "line": 25, "character": 38 }, }, "selectionRange": { "start": { "line": 15, "character": 6 }, "end": { "line": 15, "character": 15 }, }, "detail": "class SomeClass", "children": [ { "name": "__init__", "kind": 6, "range": { "start": { "line": 18, "character": 4 }, "end": { "line": 19, "character": 28 }, }, "selectionRange": { "start": { "line": 18, "character": 8 }, "end": { "line": 18, "character": 16 }, }, "detail": "def __init__", "children": [{ "name": "somedata", "kind": 7, "range": { "start": { "line": 19, "character": 8 }, "end": { "line": 19, "character": 28 }, }, "selectionRange": { "start": { "line": 19, "character": 13 }, "end": { "line": 19, "character": 21 }, }, "detail": "self.somedata = arg1", "children": [], }], }, { "name": "do_something", "kind": 6, "range": { "start": { "line": 21, "character": 4 }, "end": { "line": 22, "character": 38 }, }, "selectionRange": { "start": { "line": 21, "character": 8 }, "end": { "line": 21, "character": 20 }, }, "detail": "def do_something", "children": [], }, { "name": "so_something_else", "kind": 6, "range": { "start": { "line": 24, "character": 4 }, "end": { "line": 25, "character": 38 }, }, "selectionRange": { "start": { "line": 24, "character": 8 }, "end": { "line": 24, "character": 25 }, }, "detail": "def so_something_else", "children": [], }, ], }, ] assert_that(actual, is_(expected))
def test_publish_diagnostics_on_open(): """Tests publish diagnostics on open.""" content_path = DIAGNOSTICS_TEST_ROOT / "diagnostics_test1_contents.txt" with open(content_path, "r") as text_file: contents = text_file.read() # Introduce a syntax error before opening the file contents = contents.replace("1 == 1", "1 === 1") actual = [] with PythonFile(contents, TEMP_DIR) as py_file: uri = as_uri(py_file.fullpath) initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["workspaceFolders"] = [ {"uri": as_uri(TEMP_DIR), "name": "jedi_lsp_test"} ] with session.LspSession() as ls_session: ls_session.initialize(initialize_params) done = Event() def _handler(params): actual.append(params) done.set() ls_session.set_notification_callback( session.PUBLISH_DIAGNOSTICS, _handler ) ls_session.notify_did_open( { "textDocument": { "uri": uri, "languageId": "python", "version": 1, "text": contents, } } ) # ensure the document is loaded symbols = ls_session.text_document_symbol( {"textDocument": {"uri": uri}} ) assert len(symbols) > 0 # wait for a second to receive all notifications done.wait(1) expected = [ { "uri": uri, "diagnostics": [ { "range": { "start": {"line": 5, "character": 15}, "end": {"line": 5, "character": 16}, }, "message": "SyntaxError: invalid syntax", "severity": 1, "source": "jedi", } ], } ] assert_that(actual, is_(expected))
def test_lsp_code_action() -> None: """Tests code actions like extract variable and extract function.""" with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri((REFACTOR_TEST_ROOT / "code_action_test1.py")) actual = ls_session.text_document_code_action({ "textDocument": { "uri": uri }, "range": { "start": { "line": 4, "character": 10 }, "end": { "line": 4, "character": 10 }, }, "context": { "diagnostics": [] }, }) expected = [ { "title": StringPattern( r"Extract expression into variable 'jls_extract_var'"), "kind": "refactor.extract", "edit": { "documentChanges": [{ "textDocument": { "uri": uri, "version": 0, }, "edits": [], }] }, }, { "title": StringPattern( r"Extract expression into function 'jls_extract_def'"), "kind": "refactor.extract", "edit": { "documentChanges": [{ "textDocument": { "uri": uri, "version": 0, }, "edits": [], }] }, }, ] # Cannot use hamcrest directly for this due to unpredictable # variations in how the text edits are generated. assert_that(len(actual), is_(len(expected))) # Remove the edits actual[0]["edit"]["documentChanges"][0]["edits"] = [] actual[1]["edit"]["documentChanges"][0]["edits"] = [] assert_that(actual, is_(expected))
def test_publish_diagnostics_on_change(): """Tests publish diagnostics on change.""" with open( DIAGNOSTICS_TEST_ROOT / "diagnostics_test1_contents.txt", "r" ) as text_file: contents = text_file.read() changes = get_changes( DIAGNOSTICS_TEST_ROOT / "diagnostics_test1_content_changes.json" ) actual = [] with PythonFile(contents, TEMP_DIR) as py_file: uri = as_uri(py_file.fullpath) initialize_params = copy.deepcopy(VSCODE_DEFAULT_INITIALIZE) initialize_params["workspaceFolders"] = [ {"uri": as_uri(TEMP_DIR), "name": "jedi_lsp_test"} ] initialize_params["initializationOptions"]["diagnostics"] = { "enable": True, "didOpen": False, "didSave": False, "didChange": True, } with session.LspSession() as ls_session: ls_session.initialize(initialize_params) done = Event() def _handler(params): actual.append(params) done.set() ls_session.set_notification_callback( session.PUBLISH_DIAGNOSTICS, _handler ) ls_session.notify_did_open( { "textDocument": { "uri": uri, "languageId": "python", "version": 1, "text": contents, } } ) # ensure the document is loaded symbols = ls_session.text_document_symbol( {"textDocument": {"uri": uri}} ) assert len(symbols) > 0 # At this point there should be no published diagnostics assert_that(actual, is_([])) # Introduce a syntax error and save the file contents = contents.replace("1 == 1", "1 === 1") with open(py_file.fullpath, "w") as pyf: pyf.write(contents) # Reset waiting event just in case a diagnostic was received done.clear() # Send changes to LS version = 2 for change in changes: change["textDocument"] = {"uri": uri, "version": version} version = version + 1 ls_session.notify_did_change(change) # ensure the document is loaded symbols = ls_session.text_document_symbol( {"textDocument": {"uri": uri}} ) assert len(symbols) > 0 # wait for a second to receive all notifications done.wait(1) expected = [ { "uri": uri, "diagnostics": [ { "range": { "start": {"line": 5, "character": 15}, "end": {"line": 5, "character": 16}, }, "message": "SyntaxError: invalid syntax", "severity": 1, "source": "jedi", } ], } ] assert_that(actual, is_(expected))
def test_lsp_rename_function(): """Tests single file function rename.""" with session.LspSession() as ls_session: ls_session.initialize() uri = as_uri((REFACTOR_TEST_ROOT / "rename_test1.py")) actual = ls_session.text_document_rename({ "textDocument": { "uri": uri }, "position": { "line": 12, "character": 4 }, "newName": "my_function_1", }) expected = { "documentChanges": [{ "textDocument": { "uri": uri, "version": 0, }, "edits": [ { "range": { "start": { "line": 3, "character": 6 }, "end": { "line": 3, "character": 6 }, }, "newText": "_", }, { "range": { "start": { "line": 3, "character": 10 }, "end": { "line": 3, "character": 10 }, }, "newText": "tion_", }, { "range": { "start": { "line": 8, "character": 6 }, "end": { "line": 8, "character": 6 }, }, "newText": "_", }, { "range": { "start": { "line": 8, "character": 10 }, "end": { "line": 8, "character": 10 }, }, "newText": "tion_", }, { "range": { "start": { "line": 12, "character": 2 }, "end": { "line": 12, "character": 2 }, }, "newText": "_", }, { "range": { "start": { "line": 12, "character": 6 }, "end": { "line": 12, "character": 6 }, }, "newText": "tion_", }, ], }], } assert_that(actual, is_(expected))