diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 20:21:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 20:21:34 +0000 |
commit | fe1438b06234f8e5ecd4caa7eedfeec585b6f77c (patch) | |
tree | 5c2a9ff683189a61e0855ca3f24df319e7e03b7f /tests/lsp | |
parent | Initial commit. (diff) | |
download | pygls-fe1438b06234f8e5ecd4caa7eedfeec585b6f77c.tar.xz pygls-fe1438b06234f8e5ecd4caa7eedfeec585b6f77c.zip |
Adding upstream version 1.3.0.upstream/1.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
39 files changed, 4168 insertions, 0 deletions
diff --git a/tests/lsp/__init__.py b/tests/lsp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/lsp/__init__.py diff --git a/tests/lsp/semantic_tokens/__init__.py b/tests/lsp/semantic_tokens/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/lsp/semantic_tokens/__init__.py diff --git a/tests/lsp/semantic_tokens/test_delta_missing_legend.py b/tests/lsp/semantic_tokens/test_delta_missing_legend.py new file mode 100644 index 0000000..a3069da --- /dev/null +++ b/tests/lsp/semantic_tokens/test_delta_missing_legend.py @@ -0,0 +1,92 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, +) +from lsprotocol.types import ( + SemanticTokens, + SemanticTokensDeltaParams, + SemanticTokensLegend, + SemanticTokensPartialResult, + SemanticTokensOptionsFullType1, + TextDocumentIdentifier, +) + +from ...conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, + SemanticTokensLegend( + token_types=["keyword", "operator"], token_modifiers=["readonly"] + ), + ) + def f( + params: SemanticTokensDeltaParams, + ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]: + if params.text_document.uri == "file://return.tokens": + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + provider = capabilities.semantic_tokens_provider + assert provider.full == SemanticTokensOptionsFullType1(delta=True) + assert provider.legend.token_types == [ + "keyword", + "operator", + ] + assert provider.legend.token_modifiers == ["readonly"] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_full_delta_return_tokens(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, + SemanticTokensDeltaParams( + text_document=TextDocumentIdentifier(uri="file://return.tokens"), + previous_result_id="id", + ), + ).result() + + assert response + + assert response.data == [0, 0, 3, 0, 0] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_full_delta_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, + SemanticTokensDeltaParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + previous_result_id="id", + ), + ).result() + + assert response is None diff --git a/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py b/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py new file mode 100644 index 0000000..6f4fa17 --- /dev/null +++ b/tests/lsp/semantic_tokens/test_delta_missing_legend_none.py @@ -0,0 +1,48 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + SemanticTokens, + SemanticTokensDeltaParams, + SemanticTokensPartialResult, +) +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, +) + +from ...conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA) + def f( + params: SemanticTokensDeltaParams, + ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]: + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.semantic_tokens_provider is None + assert capabilities.semantic_tokens_provider is None diff --git a/tests/lsp/semantic_tokens/test_full_missing_legend.py b/tests/lsp/semantic_tokens/test_full_missing_legend.py new file mode 100644 index 0000000..e18dbde --- /dev/null +++ b/tests/lsp/semantic_tokens/test_full_missing_legend.py @@ -0,0 +1,46 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, +) +from lsprotocol.types import ( + SemanticTokens, + SemanticTokensPartialResult, + SemanticTokensParams, +) + +from ...conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL) + def f( + params: SemanticTokensParams, + ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]: + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + assert capabilities.semantic_tokens_provider is None diff --git a/tests/lsp/semantic_tokens/test_range.py b/tests/lsp/semantic_tokens/test_range.py new file mode 100644 index 0000000..a65504b --- /dev/null +++ b/tests/lsp/semantic_tokens/test_range.py @@ -0,0 +1,103 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, +) +from lsprotocol.types import ( + Position, + Range, + SemanticTokens, + SemanticTokensLegend, + SemanticTokensPartialResult, + SemanticTokensRangeParams, + TextDocumentIdentifier, +) + +from ...conftest import ClientServer + +SemanticTokenReturnType = Optional[ + Union[SemanticTokensPartialResult, Optional[SemanticTokens]] +] + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, + SemanticTokensLegend( + token_types=["keyword", "operator"], token_modifiers=["readonly"] + ), + ) + def f( + params: SemanticTokensRangeParams, + ) -> SemanticTokenReturnType: + if params.text_document.uri == "file://return.tokens": + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + provider = capabilities.semantic_tokens_provider + assert provider.range + assert provider.legend.token_types == [ + "keyword", + "operator", + ] + assert provider.legend.token_modifiers == ["readonly"] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_range_return_tokens(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, + SemanticTokensRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.tokens"), + range=Range( + start=Position(line=0, character=0), + end=Position(line=10, character=80), + ), + ), + ).result() + + assert response + + assert response.data == [0, 0, 3, 0, 0] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_range_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, + SemanticTokensRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + range=Range( + start=Position(line=0, character=0), + end=Position(line=10, character=80), + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/semantic_tokens/test_range_missing_legends.py b/tests/lsp/semantic_tokens/test_range_missing_legends.py new file mode 100644 index 0000000..69780ef --- /dev/null +++ b/tests/lsp/semantic_tokens/test_range_missing_legends.py @@ -0,0 +1,47 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, +) +from lsprotocol.types import ( + SemanticTokens, + SemanticTokensParams, + SemanticTokensPartialResult, +) + +from ...conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE) + def f( + params: SemanticTokensParams, + ) -> Union[SemanticTokensPartialResult, Optional[SemanticTokens]]: + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.semantic_tokens_provider is None diff --git a/tests/lsp/semantic_tokens/test_semantic_tokens_full.py b/tests/lsp/semantic_tokens/test_semantic_tokens_full.py new file mode 100644 index 0000000..dba9fa6 --- /dev/null +++ b/tests/lsp/semantic_tokens/test_semantic_tokens_full.py @@ -0,0 +1,93 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Optional, Union + +from lsprotocol.types import ( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, +) +from lsprotocol.types import ( + SemanticTokens, + SemanticTokensLegend, + SemanticTokensParams, + SemanticTokensPartialResult, + TextDocumentIdentifier, +) + +from ...conftest import ClientServer + +SemanticTokenReturnType = Optional[ + Union[SemanticTokensPartialResult, Optional[SemanticTokens]] +] + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + SemanticTokensLegend( + token_types=["keyword", "operator"], token_modifiers=["readonly"] + ), + ) + def f( + params: SemanticTokensParams, + ) -> SemanticTokenReturnType: + if params.text_document.uri == "file://return.tokens": + return SemanticTokens(data=[0, 0, 3, 0, 0]) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + provider = capabilities.semantic_tokens_provider + assert provider.full + assert provider.legend.token_types == [ + "keyword", + "operator", + ] + assert provider.legend.token_modifiers == ["readonly"] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_full_return_tokens(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + SemanticTokensParams( + text_document=TextDocumentIdentifier(uri="file://return.tokens") + ), + ).result() + + assert response + + assert response.data == [0, 0, 3, 0, 0] + + +@ConfiguredLS.decorate() +def test_semantic_tokens_full_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + SemanticTokensParams( + text_document=TextDocumentIdentifier(uri="file://return.none") + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_call_hierarchy.py b/tests/lsp/test_call_hierarchy.py new file mode 100644 index 0000000..410a982 --- /dev/null +++ b/tests/lsp/test_call_hierarchy.py @@ -0,0 +1,192 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import List, Optional + +from lsprotocol.types import ( + CALL_HIERARCHY_INCOMING_CALLS, + CALL_HIERARCHY_OUTGOING_CALLS, + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, +) +from lsprotocol.types import ( + CallHierarchyIncomingCall, + CallHierarchyIncomingCallsParams, + CallHierarchyItem, + CallHierarchyOptions, + CallHierarchyOutgoingCall, + CallHierarchyOutgoingCallsParams, + CallHierarchyPrepareParams, + Position, + Range, + SymbolKind, + SymbolTag, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + +CALL_HIERARCHY_ITEM = CallHierarchyItem( + name="test_name", + kind=SymbolKind.File, + uri="test_uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + selection_range=Range( + start=Position(line=1, character=1), + end=Position(line=2, character=2), + ), + tags=[SymbolTag.Deprecated], + detail="test_detail", + data="test_data", +) + + +def check_call_hierarchy_item_response(item): + assert item.name == "test_name" + assert item.kind == SymbolKind.File + assert item.uri == "test_uri" + assert item.range.start.line == 0 + assert item.range.start.character == 0 + assert item.range.end.line == 1 + assert item.range.end.character == 1 + assert item.selection_range.start.line == 1 + assert item.selection_range.start.character == 1 + assert item.selection_range.end.line == 2 + assert item.selection_range.end.character == 2 + assert len(item.tags) == 1 + assert item.tags[0] == SymbolTag.Deprecated + assert item.detail == "test_detail" + assert item.data == "test_data" + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, + CallHierarchyOptions(), + ) + def f1(params: CallHierarchyPrepareParams) -> Optional[List[CallHierarchyItem]]: + if params.text_document.uri == "file://return.list": + return [CALL_HIERARCHY_ITEM] + else: + return None + + @self.server.feature(CALL_HIERARCHY_INCOMING_CALLS) + def f2( + params: CallHierarchyIncomingCallsParams, + ) -> Optional[List[CallHierarchyIncomingCall]]: + return [ + CallHierarchyIncomingCall( + from_=params.item, + from_ranges=[ + Range( + start=Position(line=2, character=2), + end=Position(line=3, character=3), + ), + ], + ), + ] + + @self.server.feature(CALL_HIERARCHY_OUTGOING_CALLS) + def f3( + params: CallHierarchyOutgoingCallsParams, + ) -> Optional[List[CallHierarchyOutgoingCall]]: + return [ + CallHierarchyOutgoingCall( + to=params.item, + from_ranges=[ + Range( + start=Position(line=3, character=3), + end=Position(line=4, character=4), + ), + ], + ), + ] + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + assert capabilities.call_hierarchy_provider + + +@ConfiguredLS.decorate() +def test_call_hierarchy_prepare_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, + CallHierarchyPrepareParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + position=Position(line=0, character=0), + ), + ).result() + + check_call_hierarchy_item_response(response[0]) + + +@ConfiguredLS.decorate() +def test_call_hierarchy_prepare_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, + CallHierarchyPrepareParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None + + +@ConfiguredLS.decorate() +def test_call_hierarchy_incoming_calls_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + CALL_HIERARCHY_INCOMING_CALLS, + CallHierarchyIncomingCallsParams(item=CALL_HIERARCHY_ITEM), + ).result() + + item = response[0] + + check_call_hierarchy_item_response(item.from_) + + assert item.from_ranges[0].start.line == 2 + assert item.from_ranges[0].start.character == 2 + assert item.from_ranges[0].end.line == 3 + assert item.from_ranges[0].end.character == 3 + + +@ConfiguredLS.decorate() +def test_call_hierarchy_outgoing_calls_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + CALL_HIERARCHY_OUTGOING_CALLS, + CallHierarchyOutgoingCallsParams(item=CALL_HIERARCHY_ITEM), + ).result() + + item = response[0] + + check_call_hierarchy_item_response(item.to) + + assert item.from_ranges[0].start.line == 3 + assert item.from_ranges[0].start.character == 3 + assert item.from_ranges[0].end.line == 4 + assert item.from_ranges[0].end.character == 4 diff --git a/tests/lsp/test_code_action.py b/tests/lsp/test_code_action.py new file mode 100644 index 0000000..c10dcd5 --- /dev/null +++ b/tests/lsp/test_code_action.py @@ -0,0 +1,60 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Tuple + +from lsprotocol import types + +from ..client import LanguageClient + + +async def test_code_actions( + code_action_client: Tuple[LanguageClient, types.InitializeResult], uri_for +): + """Ensure that the example code action server is working as expected.""" + client, initialize_result = code_action_client + + code_action_options = initialize_result.capabilities.code_action_provider + assert code_action_options.code_action_kinds == [types.CodeActionKind.QuickFix] + + test_uri = uri_for("sums.txt") + assert test_uri is not None + + response = await client.text_document_code_action_async( + types.CodeActionParams( + text_document=types.TextDocumentIdentifier(uri=test_uri), + range=types.Range( + start=types.Position(line=0, character=0), + end=types.Position(line=1, character=0), + ), + context=types.CodeActionContext(diagnostics=[]), + ) + ) + + assert len(response) == 1 + code_action = response[0] + + assert code_action.title == "Evaluate '1 + 1 ='" + assert code_action.kind == types.CodeActionKind.QuickFix + + fix = code_action.edit.changes[test_uri][0] + expected_range = types.Range( + start=types.Position(line=0, character=0), + end=types.Position(line=0, character=7), + ) + + assert fix.range == expected_range + assert fix.new_text == "1 + 1 = 2!" diff --git a/tests/lsp/test_code_lens.py b/tests/lsp/test_code_lens.py new file mode 100644 index 0000000..7024110 --- /dev/null +++ b/tests/lsp/test_code_lens.py @@ -0,0 +1,94 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_CODE_LENS +from lsprotocol.types import ( + CodeLens, + CodeLensOptions, + CodeLensParams, + Command, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_CODE_LENS, + CodeLensOptions(resolve_provider=False), + ) + def f(params: CodeLensParams) -> Optional[List[CodeLens]]: + if params.text_document.uri == "file://return.list": + return [ + CodeLens( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + command=Command( + title="cmd1", + command="cmd1", + ), + data="some data", + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.code_lens_provider + assert not capabilities.code_lens_provider.resolve_provider + + +@ConfiguredLS.decorate() +def test_code_lens_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams(text_document=TextDocumentIdentifier(uri="file://return.list")), + ).result() + + assert response[0].data == "some data" + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + assert response[0].command.title == "cmd1" + assert response[0].command.command == "cmd1" + + +@ConfiguredLS.decorate() +def test_code_lens_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams(text_document=TextDocumentIdentifier(uri="file://return.none")), + ).result() + + assert response is None diff --git a/tests/lsp/test_color_presentation.py b/tests/lsp/test_color_presentation.py new file mode 100644 index 0000000..6748e66 --- /dev/null +++ b/tests/lsp/test_color_presentation.py @@ -0,0 +1,103 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List + +from lsprotocol.types import TEXT_DOCUMENT_COLOR_PRESENTATION +from lsprotocol.types import ( + Color, + ColorPresentation, + ColorPresentationParams, + Position, + Range, + TextDocumentIdentifier, + TextEdit, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_COLOR_PRESENTATION) + def f(params: ColorPresentationParams) -> List[ColorPresentation]: + return [ + ColorPresentation( + label="label1", + text_edit=TextEdit( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + new_text="te", + ), + additional_text_edits=[ + TextEdit( + range=Range( + start=Position(line=1, character=1), + end=Position(line=2, character=2), + ), + new_text="ate1", + ), + TextEdit( + range=Range( + start=Position(line=2, character=2), + end=Position(line=3, character=3), + ), + new_text="ate2", + ), + ], + ) + ] + + +@ConfiguredLS.decorate() +def test_color_presentation(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_COLOR_PRESENTATION, + ColorPresentationParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + color=Color(red=0.6, green=0.2, blue=0.3, alpha=0.5), + range=Range( + start=Position(line=0, character=0), + end=Position(line=3, character=3), + ), + ), + ).result() + + assert response[0].label == "label1" + assert response[0].text_edit.new_text == "te" + + assert response[0].text_edit.range.start.line == 0 + assert response[0].text_edit.range.start.character == 0 + assert response[0].text_edit.range.end.line == 1 + assert response[0].text_edit.range.end.character == 1 + + range = response[0].additional_text_edits[0].range + assert range.start.line == 1 + assert range.start.character == 1 + assert range.end.line == 2 + assert range.end.character == 2 + + range = response[0].additional_text_edits[1].range + assert range.start.line == 2 + assert range.start.character == 2 + assert range.end.line == 3 + assert range.end.character == 3 diff --git a/tests/lsp/test_completion.py b/tests/lsp/test_completion.py new file mode 100644 index 0000000..dcb8124 --- /dev/null +++ b/tests/lsp/test_completion.py @@ -0,0 +1,48 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Tuple + +from lsprotocol import types + + +from ..client import LanguageClient + + +async def test_completion( + json_server_client: Tuple[LanguageClient, types.InitializeResult], + uri_for, +): + """Ensure that the completion methods are working as expected.""" + client, initialize_result = json_server_client + + completion_provider = initialize_result.capabilities.completion_provider + assert completion_provider + assert completion_provider.trigger_characters == [","] + assert completion_provider.all_commit_characters == [":"] + + test_uri = uri_for("example.json") + assert test_uri is not None + + response = await client.text_document_completion_async( + types.CompletionParams( + text_document=types.TextDocumentIdentifier(uri=test_uri), + position=types.Position(line=0, character=0), + ) + ) + + labels = {i.label for i in response.items} + assert labels == set(['"', "[", "]", "{", "}"]) diff --git a/tests/lsp/test_declaration.py b/tests/lsp/test_declaration.py new file mode 100644 index 0000000..221982d --- /dev/null +++ b/tests/lsp/test_declaration.py @@ -0,0 +1,161 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional, Union + +from lsprotocol.types import TEXT_DOCUMENT_DECLARATION +from lsprotocol.types import ( + DeclarationOptions, + DeclarationParams, + Location, + LocationLink, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_DECLARATION, DeclarationOptions()) + def f( + params: DeclarationParams, + ) -> Optional[Union[Location, List[Location], List[LocationLink]]]: + location = Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ) + + location_link = LocationLink( + target_uri="uri", + target_range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + target_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=2, character=2), + ), + origin_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=3, character=3), + ), + ) + + return { # type: ignore + "file://return.location": location, + "file://return.location_list": [location], + "file://return.location_link_list": [location_link], + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.declaration_provider + + +@ConfiguredLS.decorate() +def test_declaration_return_location(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DECLARATION, + DeclarationParams( + text_document=TextDocumentIdentifier(uri="file://return.location"), + position=Position(line=0, character=0), + ), + ).result() + + assert response.uri == "uri" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_declaration_return_location_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DECLARATION, + DeclarationParams( + text_document=TextDocumentIdentifier(uri="file://return.location_list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].uri == "uri" + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_declaration_return_location_link_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DECLARATION, + DeclarationParams( + text_document=TextDocumentIdentifier( + uri="file://return.location_link_list" + ), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].target_uri == "uri" + + assert response[0].target_range.start.line == 0 + assert response[0].target_range.start.character == 0 + assert response[0].target_range.end.line == 1 + assert response[0].target_range.end.character == 1 + + assert response[0].target_selection_range.start.line == 0 + assert response[0].target_selection_range.start.character == 0 + assert response[0].target_selection_range.end.line == 2 + assert response[0].target_selection_range.end.character == 2 + + assert response[0].origin_selection_range.start.line == 0 + assert response[0].origin_selection_range.start.character == 0 + assert response[0].origin_selection_range.end.line == 3 + assert response[0].origin_selection_range.end.character == 3 + + +@ConfiguredLS.decorate() +def test_declaration_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DECLARATION, + DeclarationParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_definition.py b/tests/lsp/test_definition.py new file mode 100644 index 0000000..3ed2f96 --- /dev/null +++ b/tests/lsp/test_definition.py @@ -0,0 +1,164 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional, Union + +from lsprotocol.types import TEXT_DOCUMENT_DEFINITION +from lsprotocol.types import ( + DefinitionOptions, + DefinitionParams, + Location, + LocationLink, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_DEFINITION, + DefinitionOptions(), + ) + def f( + params: DefinitionParams, + ) -> Optional[Union[Location, List[Location], List[LocationLink]]]: + location = Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ) + + location_link = LocationLink( + target_uri="uri", + target_range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + target_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=2, character=2), + ), + origin_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=3, character=3), + ), + ) + + return { # type: ignore + "file://return.location": location, + "file://return.location_list": [location], + "file://return.location_link_list": [location_link], + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.definition_provider is not None + + +@ConfiguredLS.decorate() +def test_definition_return_location(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DEFINITION, + DefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.location"), + position=Position(line=0, character=0), + ), + ).result() + + assert response.uri == "uri" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_definition_return_location_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DEFINITION, + DefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.location_list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].uri == "uri" + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_definition_return_location_link_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DEFINITION, + DefinitionParams( + text_document=TextDocumentIdentifier( + uri="file://return.location_link_list" + ), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].target_uri == "uri" + + assert response[0].target_range.start.line == 0 + assert response[0].target_range.start.character == 0 + assert response[0].target_range.end.line == 1 + assert response[0].target_range.end.character == 1 + + assert response[0].target_selection_range.start.line == 0 + assert response[0].target_selection_range.start.character == 0 + assert response[0].target_selection_range.end.line == 2 + assert response[0].target_selection_range.end.character == 2 + + assert response[0].origin_selection_range.start.line == 0 + assert response[0].origin_selection_range.start.character == 0 + assert response[0].origin_selection_range.end.line == 3 + assert response[0].origin_selection_range.end.character == 3 + + +@ConfiguredLS.decorate() +def test_definition_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DEFINITION, + DefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_diagnostics.py b/tests/lsp/test_diagnostics.py new file mode 100644 index 0000000..c420942 --- /dev/null +++ b/tests/lsp/test_diagnostics.py @@ -0,0 +1,68 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +import json +from typing import Tuple + +from lsprotocol import types + + +from ..client import LanguageClient + + +async def test_diagnostics( + json_server_client: Tuple[LanguageClient, types.InitializeResult], + uri_for, +): + """Ensure that diagnostics are working as expected.""" + client, _ = json_server_client + + test_uri = uri_for("example.json") + assert test_uri is not None + + # Get the expected error message + document_content = "text" + try: + json.loads(document_content) + except json.JSONDecodeError as err: + expected_message = err.msg + + client.text_document_did_open( + types.DidOpenTextDocumentParams( + text_document=types.TextDocumentItem( + uri=test_uri, language_id="json", version=1, text=document_content + ) + ) + ) + + await client.wait_for_notification(types.TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS) + + diagnostics = client.diagnostics[test_uri] + assert diagnostics[0].message == expected_message + + result = await client.text_document_diagnostic_async( + types.DocumentDiagnosticParams( + text_document=types.TextDocumentIdentifier(test_uri) + ) + ) + diagnostics = result.items + assert diagnostics[0].message == expected_message + + workspace_result = await client.workspace_diagnostic_async( + types.WorkspaceDiagnosticParams(previous_result_ids=[]) + ) + diagnostics = workspace_result.items[0].items + assert diagnostics[0].message == expected_message diff --git a/tests/lsp/test_document_color.py b/tests/lsp/test_document_color.py new file mode 100644 index 0000000..460a60b --- /dev/null +++ b/tests/lsp/test_document_color.py @@ -0,0 +1,81 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List + +from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_COLOR +from lsprotocol.types import ( + Color, + ColorInformation, + DocumentColorOptions, + DocumentColorParams, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_DOCUMENT_COLOR, + DocumentColorOptions(), + ) + def f(params: DocumentColorParams) -> List[ColorInformation]: + return [ + ColorInformation( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + color=Color(red=0.5, green=0.5, blue=0.5, alpha=0.5), + ) + ] + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.color_provider + + +@ConfiguredLS.decorate() +def test_document_color(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_COLOR, + DocumentColorParams( + text_document=TextDocumentIdentifier(uri="file://return.list") + ), + ).result() + + assert response + assert response[0].color.red == 0.5 + assert response[0].color.green == 0.5 + assert response[0].color.blue == 0.5 + assert response[0].color.alpha == 0.5 + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 diff --git a/tests/lsp/test_document_highlight.py b/tests/lsp/test_document_highlight.py new file mode 100644 index 0000000..e4afc5f --- /dev/null +++ b/tests/lsp/test_document_highlight.py @@ -0,0 +1,108 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT +from lsprotocol.types import ( + DocumentHighlight, + DocumentHighlightKind, + DocumentHighlightOptions, + DocumentHighlightParams, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, + DocumentHighlightOptions(), + ) + def f(params: DocumentHighlightParams) -> Optional[List[DocumentHighlight]]: + if params.text_document.uri == "file://return.list": + return [ + DocumentHighlight( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ), + DocumentHighlight( + range=Range( + start=Position(line=1, character=1), + end=Position(line=2, character=2), + ), + kind=DocumentHighlightKind.Write, + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_highlight_provider + + +@ConfiguredLS.decorate() +def test_document_highlight_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, + DocumentHighlightParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + assert response[0].kind is None + + assert response[1].range.start.line == 1 + assert response[1].range.start.character == 1 + assert response[1].range.end.line == 2 + assert response[1].range.end.character == 2 + assert response[1].kind == DocumentHighlightKind.Write + + +@ConfiguredLS.decorate() +def test_document_highlight_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, + DocumentHighlightParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_document_link.py b/tests/lsp/test_document_link.py new file mode 100644 index 0000000..0602773 --- /dev/null +++ b/tests/lsp/test_document_link.py @@ -0,0 +1,98 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_LINK +from lsprotocol.types import ( + DocumentLink, + DocumentLinkOptions, + DocumentLinkParams, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_DOCUMENT_LINK, + DocumentLinkOptions(resolve_provider=True), + ) + def f(params: DocumentLinkParams) -> Optional[List[DocumentLink]]: + if params.text_document.uri == "file://return.list": + return [ + DocumentLink( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + target="target", + tooltip="tooltip", + data="data", + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_link_provider + assert capabilities.document_link_provider.resolve_provider + + +@ConfiguredLS.decorate() +def test_document_link_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_LINK, + DocumentLinkParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + ), + ).result() + + assert response + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + assert response[0].target == "target" + assert response[0].tooltip == "tooltip" + assert response[0].data == "data" + + +@ConfiguredLS.decorate() +def test_document_link_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_LINK, + DocumentLinkParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_document_symbol.py b/tests/lsp/test_document_symbol.py new file mode 100644 index 0000000..251c8fb --- /dev/null +++ b/tests/lsp/test_document_symbol.py @@ -0,0 +1,168 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Union + +from lsprotocol.types import TEXT_DOCUMENT_DOCUMENT_SYMBOL +from lsprotocol.types import ( + DocumentSymbol, + DocumentSymbolOptions, + DocumentSymbolParams, + Location, + Position, + Range, + SymbolInformation, + SymbolKind, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_DOCUMENT_SYMBOL, + DocumentSymbolOptions(), + ) + def f( + params: DocumentSymbolParams, + ) -> Union[List[SymbolInformation], List[DocumentSymbol]]: + symbol_info = SymbolInformation( + name="symbol", + kind=SymbolKind.Namespace, + location=Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ), + container_name="container", + deprecated=False, + ) + + document_symbol_inner = DocumentSymbol( + name="inner_symbol", + kind=SymbolKind.Number, + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ) + + document_symbol = DocumentSymbol( + name="symbol", + kind=SymbolKind.Object, + range=Range( + start=Position(line=0, character=0), + end=Position(line=10, character=10), + ), + selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=10, character=10), + ), + detail="detail", + children=[document_symbol_inner], + deprecated=True, + ) + + return { # type: ignore + "file://return.symbol_information_list": [symbol_info], + "file://return.document_symbol_list": [document_symbol], + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_symbol_provider + + +@ConfiguredLS.decorate() +def test_document_symbol_return_symbol_information_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_SYMBOL, + DocumentSymbolParams( + text_document=TextDocumentIdentifier( + uri="file://return.symbol_information_list" + ), + ), + ).result() + + assert response + + assert response[0].name == "symbol" + assert response[0].kind == SymbolKind.Namespace + assert response[0].location.uri == "uri" + assert response[0].location.range.start.line == 0 + assert response[0].location.range.start.character == 0 + assert response[0].location.range.end.line == 1 + assert response[0].location.range.end.character == 1 + assert response[0].container_name == "container" + assert not response[0].deprecated + + +@ConfiguredLS.decorate() +def test_document_symbol_return_document_symbol_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_DOCUMENT_SYMBOL, + DocumentSymbolParams( + text_document=TextDocumentIdentifier( + uri="file://return.document_symbol_list" + ), + ), + ).result() + + assert response + + assert response[0].name == "symbol" + assert response[0].kind == SymbolKind.Object + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 10 + assert response[0].range.end.character == 10 + assert response[0].selection_range.start.line == 0 + assert response[0].selection_range.start.character == 0 + assert response[0].selection_range.end.line == 10 + assert response[0].selection_range.end.character == 10 + assert response[0].detail == "detail" + assert response[0].deprecated + + assert response[0].children[0].name == "inner_symbol" + assert response[0].children[0].kind == SymbolKind.Number + assert response[0].children[0].range.start.line == 0 + assert response[0].children[0].range.start.character == 0 + assert response[0].children[0].range.end.line == 1 + assert response[0].children[0].range.end.character == 1 + range = response[0].children[0].selection_range + assert range.start.line == 0 + assert range.start.character == 0 + assert range.end.line == 1 + assert range.end.character == 1 + + assert response[0].children[0].children is None diff --git a/tests/lsp/test_errors.py b/tests/lsp/test_errors.py new file mode 100644 index 0000000..1fbc05b --- /dev/null +++ b/tests/lsp/test_errors.py @@ -0,0 +1,135 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Any, List, Union +import time + +import pytest + +from pygls.exceptions import JsonRpcInternalError, PyglsError, JsonRpcException +from lsprotocol.types import WINDOW_SHOW_MESSAGE, MessageType +from pygls.server import LanguageServer + +from ..conftest import ClientServer + +ERROR_TRIGGER = "test/triggerError" +ERROR_MESSAGE = "Testing errors" + + +class CustomLanguageServerSafe(LanguageServer): + def report_server_error( + self, error: Exception, source: Union[PyglsError, JsonRpcException] + ): + pass + + +class CustomLanguageServerPotentialRecursion(LanguageServer): + def report_server_error( + self, error: Exception, source: Union[PyglsError, JsonRpcException] + ): + raise Exception() + + +class CustomLanguageServerSendAll(LanguageServer): + def report_server_error( + self, error: Exception, source: Union[PyglsError, JsonRpcException] + ): + self.show_message(self.default_error_message, msg_type=MessageType.Error) + + +class ConfiguredLS(ClientServer): + def __init__(self, LS=LanguageServer): + super().__init__(LS) + self.init() + + def init(self): + self.client.messages: List[str] = [] + + @self.server.feature(ERROR_TRIGGER) + def f1(params: Any): + raise Exception(ERROR_MESSAGE) + + @self.client.feature(WINDOW_SHOW_MESSAGE) + def f2(params: Any): + self.client.messages.append(params.message) + + +class CustomConfiguredLSSafe(ConfiguredLS): + def __init__(self): + super().__init__(CustomLanguageServerSafe) + + +class CustomConfiguredLSPotentialRecusrion(ConfiguredLS): + def __init__(self): + super().__init__(CustomLanguageServerPotentialRecursion) + + +class CustomConfiguredLSSendAll(ConfiguredLS): + def __init__(self): + super().__init__(CustomLanguageServerSendAll) + + +@ConfiguredLS.decorate() +def test_request_error_reporting_default(client_server): + client, _ = client_server + assert len(client.messages) == 0 + + with pytest.raises(JsonRpcInternalError, match=ERROR_MESSAGE): + client.lsp.send_request(ERROR_TRIGGER).result() + + time.sleep(0.1) + assert len(client.messages) == 0 + + +@CustomConfiguredLSSendAll.decorate() +def test_request_error_reporting_override(client_server): + client, _ = client_server + assert len(client.messages) == 0 + + with pytest.raises(JsonRpcInternalError, match=ERROR_MESSAGE): + client.lsp.send_request(ERROR_TRIGGER).result() + + time.sleep(0.1) + assert len(client.messages) == 1 + + +@ConfiguredLS.decorate() +def test_notification_error_reporting(client_server): + client, _ = client_server + client.lsp.notify(ERROR_TRIGGER) + time.sleep(0.1) + + assert len(client.messages) == 1 + assert client.messages[0] == LanguageServer.default_error_message + + +@CustomConfiguredLSSafe.decorate() +def test_overriding_error_reporting(client_server): + client, _ = client_server + client.lsp.notify(ERROR_TRIGGER) + time.sleep(0.1) + + assert len(client.messages) == 0 + + +@CustomConfiguredLSPotentialRecusrion.decorate() +def test_overriding_error_reporting_with_potential_recursion(client_server): + client, _ = client_server + client.lsp.notify(ERROR_TRIGGER) + time.sleep(0.1) + + assert len(client.messages) == 0 diff --git a/tests/lsp/test_folding_range.py b/tests/lsp/test_folding_range.py new file mode 100644 index 0000000..8f9c749 --- /dev/null +++ b/tests/lsp/test_folding_range.py @@ -0,0 +1,92 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_FOLDING_RANGE +from lsprotocol.types import ( + FoldingRange, + FoldingRangeKind, + FoldingRangeOptions, + FoldingRangeParams, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_FOLDING_RANGE, + FoldingRangeOptions(), + ) + def f(params: FoldingRangeParams) -> Optional[List[FoldingRange]]: + if params.text_document.uri == "file://return.list": + return [ + FoldingRange( + start_line=0, + end_line=0, + start_character=1, + end_character=1, + kind=FoldingRangeKind.Comment, + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.folding_range_provider + + +@ConfiguredLS.decorate() +def test_folding_range_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_FOLDING_RANGE, + FoldingRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + ), + ).result() + + assert response + + assert response[0].start_line == 0 + assert response[0].end_line == 0 + assert response[0].start_character == 1 + assert response[0].end_character == 1 + assert response[0].kind == FoldingRangeKind.Comment + + +@ConfiguredLS.decorate() +def test_folding_range_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_FOLDING_RANGE, + FoldingRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_formatting.py b/tests/lsp/test_formatting.py new file mode 100644 index 0000000..0c3fbd6 --- /dev/null +++ b/tests/lsp/test_formatting.py @@ -0,0 +1,108 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_FORMATTING +from lsprotocol.types import ( + DocumentFormattingOptions, + DocumentFormattingParams, + FormattingOptions, + Position, + Range, + TextDocumentIdentifier, + TextEdit, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_FORMATTING, + DocumentFormattingOptions(), + ) + def f(params: DocumentFormattingParams) -> Optional[List[TextEdit]]: + if params.text_document.uri == "file://return.list": + return [ + TextEdit( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + new_text="text", + ) + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_formatting_provider + + +@ConfiguredLS.decorate() +def test_document_formatting_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_FORMATTING, + DocumentFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response + + assert response[0].new_text == "text" + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_document_formatting_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_FORMATTING, + DocumentFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_hover.py b/tests/lsp/test_hover.py new file mode 100644 index 0000000..9007c78 --- /dev/null +++ b/tests/lsp/test_hover.py @@ -0,0 +1,149 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Optional + +from lsprotocol.types import TEXT_DOCUMENT_HOVER +from lsprotocol.types import ( + Hover, + HoverOptions, + HoverParams, + MarkedString_Type1, + MarkupContent, + MarkupKind, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_HOVER, + HoverOptions(), + ) + def f(params: HoverParams) -> Optional[Hover]: + range = Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ) + + return { + "file://return.marked_string": Hover( + range=range, + contents=MarkedString_Type1( + language="language", + value="value", + ), + ), + "file://return.marked_string_list": Hover( + range=range, + contents=[ + MarkedString_Type1( + language="language", + value="value", + ), + "str type", + ], + ), + "file://return.markup_content": Hover( + range=range, + contents=MarkupContent(kind=MarkupKind.Markdown, value="value"), + ), + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.hover_provider + + +@ConfiguredLS.decorate() +def test_hover_return_marked_string(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_HOVER, + HoverParams( + text_document=TextDocumentIdentifier(uri="file://return.marked_string"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.contents.language == "language" + assert response.contents.value == "value" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_hover_return_marked_string_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_HOVER, + HoverParams( + text_document=TextDocumentIdentifier( + uri="file://return.marked_string_list" + ), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.contents[0].language == "language" + assert response.contents[0].value == "value" + assert response.contents[1] == "str type" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_hover_return_markup_content(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_HOVER, + HoverParams( + text_document=TextDocumentIdentifier(uri="file://return.markup_content"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.contents.kind == MarkupKind.Markdown + assert response.contents.value == "value" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 diff --git a/tests/lsp/test_implementation.py b/tests/lsp/test_implementation.py new file mode 100644 index 0000000..4fea3a9 --- /dev/null +++ b/tests/lsp/test_implementation.py @@ -0,0 +1,164 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional, Union + +from lsprotocol.types import TEXT_DOCUMENT_IMPLEMENTATION +from lsprotocol.types import ( + ImplementationOptions, + ImplementationParams, + Location, + LocationLink, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_IMPLEMENTATION, + ImplementationOptions(), + ) + def f( + params: ImplementationParams, + ) -> Optional[Union[Location, List[Location], List[LocationLink]]]: + location = Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ) + + location_link = LocationLink( + target_uri="uri", + target_range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + target_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=2, character=2), + ), + origin_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=3, character=3), + ), + ) + + return { # type: ignore + "file://return.location": location, + "file://return.location_list": [location], + "file://return.location_link_list": [location_link], + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.implementation_provider + + +@ConfiguredLS.decorate() +def test_type_definition_return_location(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_IMPLEMENTATION, + ImplementationParams( + text_document=TextDocumentIdentifier(uri="file://return.location"), + position=Position(line=0, character=0), + ), + ).result() + + assert response.uri == "uri" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_type_definition_return_location_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_IMPLEMENTATION, + ImplementationParams( + text_document=TextDocumentIdentifier(uri="file://return.location_list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].uri == "uri" + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_type_definition_return_location_link_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_IMPLEMENTATION, + ImplementationParams( + text_document=TextDocumentIdentifier( + uri="file://return.location_link_list" + ), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].target_uri == "uri" + + assert response[0].target_range.start.line == 0 + assert response[0].target_range.start.character == 0 + assert response[0].target_range.end.line == 1 + assert response[0].target_range.end.character == 1 + + assert response[0].target_selection_range.start.line == 0 + assert response[0].target_selection_range.start.character == 0 + assert response[0].target_selection_range.end.line == 2 + assert response[0].target_selection_range.end.character == 2 + + assert response[0].origin_selection_range.start.line == 0 + assert response[0].origin_selection_range.start.character == 0 + assert response[0].origin_selection_range.end.line == 3 + assert response[0].origin_selection_range.end.character == 3 + + +@ConfiguredLS.decorate() +def test_type_definition_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_IMPLEMENTATION, + ImplementationParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_inlay_hints.py b/tests/lsp/test_inlay_hints.py new file mode 100644 index 0000000..1146e1b --- /dev/null +++ b/tests/lsp/test_inlay_hints.py @@ -0,0 +1,56 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Tuple + +from lsprotocol import types + +from ..client import LanguageClient + + +async def test_code_actions( + inlay_hints_client: Tuple[LanguageClient, types.InitializeResult], uri_for +): + """Ensure that the example code action server is working as expected.""" + client, initialize_result = inlay_hints_client + + inlay_hint_provider = initialize_result.capabilities.inlay_hint_provider + assert inlay_hint_provider.resolve_provider is True + + test_uri = uri_for("sums.txt") + assert test_uri is not None + + response = await client.text_document_inlay_hint_async( + types.InlayHintParams( + text_document=types.TextDocumentIdentifier(uri=test_uri), + range=types.Range( + start=types.Position(line=3, character=0), + end=types.Position(line=4, character=0), + ), + ) + ) + + assert len(response) == 2 + two, three = response[0], response[1] + + assert two.label == ":10" + assert two.tooltip is None + + assert three.label == ":11" + assert three.tooltip is None + + resolved = await client.inlay_hint_resolve_async(three) + assert resolved.tooltip == "Binary representation of the number: 3" diff --git a/tests/lsp/test_inline_value.py b/tests/lsp/test_inline_value.py new file mode 100644 index 0000000..68d682d --- /dev/null +++ b/tests/lsp/test_inline_value.py @@ -0,0 +1,60 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import Tuple + +from lsprotocol import types + + +from ..client import LanguageClient + + +async def test_inline_value( + json_server_client: Tuple[LanguageClient, types.InitializeResult], + uri_for, +): + """Ensure that inline values are working as expected.""" + client, _ = json_server_client + + test_uri = uri_for("example.json") + assert test_uri is not None + + document_content = '{\n"foo": "bar"\n}' + client.text_document_did_open( + types.DidOpenTextDocumentParams( + text_document=types.TextDocumentItem( + uri=test_uri, language_id="json", version=1, text=document_content + ) + ) + ) + + result = await client.text_document_inline_value_async( + types.InlineValueParams( + text_document=types.TextDocumentIdentifier(test_uri), + range=types.Range( + start=types.Position(line=1, character=0), + end=types.Position(line=1, character=6), + ), + context=types.InlineValueContext( + frame_id=1, + stopped_location=types.Range( + start=types.Position(line=1, character=0), + end=types.Position(line=1, character=6), + ), + ), + ) + ) + assert result[0].text == "Inline value" diff --git a/tests/lsp/test_linked_editing_range.py b/tests/lsp/test_linked_editing_range.py new file mode 100644 index 0000000..2650b7e --- /dev/null +++ b/tests/lsp/test_linked_editing_range.py @@ -0,0 +1,103 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Optional + +from lsprotocol.types import TEXT_DOCUMENT_LINKED_EDITING_RANGE +from lsprotocol.types import ( + LinkedEditingRangeOptions, + LinkedEditingRangeParams, + LinkedEditingRanges, + Position, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_LINKED_EDITING_RANGE, + LinkedEditingRangeOptions(), + ) + def f(params: LinkedEditingRangeParams) -> Optional[LinkedEditingRanges]: + if params.text_document.uri == "file://return.ranges": + return LinkedEditingRanges( + ranges=[ + Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + Range( + start=Position(line=1, character=1), + end=Position(line=2, character=2), + ), + ], + word_pattern="pattern", + ) + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.linked_editing_range_provider + + +@ConfiguredLS.decorate() +def test_linked_editing_ranges_return_ranges(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_LINKED_EDITING_RANGE, + LinkedEditingRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.ranges"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.ranges[0].start.line == 0 + assert response.ranges[0].start.character == 0 + assert response.ranges[0].end.line == 1 + assert response.ranges[0].end.character == 1 + assert response.ranges[1].start.line == 1 + assert response.ranges[1].start.character == 1 + assert response.ranges[1].end.line == 2 + assert response.ranges[1].end.character == 2 + assert response.word_pattern == "pattern" + + +@ConfiguredLS.decorate() +def test_linked_editing_ranges_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_LINKED_EDITING_RANGE, + LinkedEditingRangeParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_moniker.py b/tests/lsp/test_moniker.py new file mode 100644 index 0000000..09b962d --- /dev/null +++ b/tests/lsp/test_moniker.py @@ -0,0 +1,94 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_MONIKER +from lsprotocol.types import ( + Moniker, + MonikerKind, + MonikerOptions, + MonikerParams, + Position, + TextDocumentIdentifier, + UniquenessLevel, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_MONIKER, + MonikerOptions(), + ) + def f(params: MonikerParams) -> Optional[List[Moniker]]: + if params.text_document.uri == "file://return.list": + return [ + Moniker( + scheme="test_scheme", + identifier="test_identifier", + unique=UniquenessLevel.Global, + kind=MonikerKind.Local, + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.moniker_provider + + +@ConfiguredLS.decorate() +def test_moniker_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_MONIKER, + MonikerParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response[0].scheme == "test_scheme" + assert response[0].identifier == "test_identifier" + assert response[0].unique == UniquenessLevel.Global + assert response[0].kind == MonikerKind.Local + + +@ConfiguredLS.decorate() +def test_references_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_MONIKER, + MonikerParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_on_type_formatting.py b/tests/lsp/test_on_type_formatting.py new file mode 100644 index 0000000..2e7adc9 --- /dev/null +++ b/tests/lsp/test_on_type_formatting.py @@ -0,0 +1,122 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_ON_TYPE_FORMATTING +from lsprotocol.types import ( + DocumentOnTypeFormattingOptions, + DocumentOnTypeFormattingParams, + FormattingOptions, + Position, + Range, + TextDocumentIdentifier, + TextEdit, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_ON_TYPE_FORMATTING, + DocumentOnTypeFormattingOptions( + first_trigger_character=":", + more_trigger_character=[",", "."], + ), + ) + def f(params: DocumentOnTypeFormattingParams) -> Optional[List[TextEdit]]: + if params.text_document.uri == "file://return.list": + return [ + TextEdit( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + new_text="text", + ) + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_on_type_formatting_provider + assert ( + capabilities.document_on_type_formatting_provider.first_trigger_character == ":" + ) + assert capabilities.document_on_type_formatting_provider.more_trigger_character == [ + ",", + ".", + ] + + +@ConfiguredLS.decorate() +def test_on_type_formatting_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_ON_TYPE_FORMATTING, + DocumentOnTypeFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + position=Position(line=0, character=0), + ch=":", + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response + + assert response[0].new_text == "text" + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_on_type_formatting_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_ON_TYPE_FORMATTING, + DocumentOnTypeFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ch=":", + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_prepare_rename.py b/tests/lsp/test_prepare_rename.py new file mode 100644 index 0000000..39d0712 --- /dev/null +++ b/tests/lsp/test_prepare_rename.py @@ -0,0 +1,111 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Optional, Union + +from lsprotocol.types import TEXT_DOCUMENT_PREPARE_RENAME +from lsprotocol.types import ( + Position, + PrepareRenameResult, + PrepareRenameResult_Type1, + PrepareRenameParams, + Range, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(TEXT_DOCUMENT_PREPARE_RENAME) + def f( + params: PrepareRenameParams, + ) -> Optional[Union[Range, PrepareRenameResult]]: + return { # type: ignore + "file://return.range": Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + "file://return.prepare_rename": PrepareRenameResult_Type1( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + placeholder="placeholder", + ), + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + pass + + +@ConfiguredLS.decorate() +def test_prepare_rename_return_range(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_PREPARE_RENAME, + PrepareRenameParams( + text_document=TextDocumentIdentifier(uri="file://return.range"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.start.line == 0 + assert response.start.character == 0 + assert response.end.line == 1 + assert response.end.character == 1 + + +@ConfiguredLS.decorate() +def test_prepare_rename_return_prepare_rename(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_PREPARE_RENAME, + PrepareRenameParams( + text_document=TextDocumentIdentifier(uri="file://return.prepare_rename"), + position=Position(line=0, character=0), + ), + ).result() + + assert response + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + assert response.placeholder == "placeholder" + + +@ConfiguredLS.decorate() +def test_prepare_rename_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_PREPARE_RENAME, + PrepareRenameParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_progress.py b/tests/lsp/test_progress.py new file mode 100644 index 0000000..4965772 --- /dev/null +++ b/tests/lsp/test_progress.py @@ -0,0 +1,225 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +import asyncio +from typing import List, Optional + +import pytest +from lsprotocol.types import ( + TEXT_DOCUMENT_CODE_LENS, + WINDOW_WORK_DONE_PROGRESS_CANCEL, + WINDOW_WORK_DONE_PROGRESS_CREATE, + PROGRESS, +) +from lsprotocol.types import ( + CodeLens, + CodeLensParams, + CodeLensOptions, + ProgressParams, + TextDocumentIdentifier, + WorkDoneProgressBegin, + WorkDoneProgressEnd, + WorkDoneProgressReport, + WorkDoneProgressCancelParams, + WorkDoneProgressCreateParams, +) +from ..conftest import ClientServer +from pygls import IS_PYODIDE + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + self.client.notifications: List[ProgressParams] = [] + self.client.method_calls: List[WorkDoneProgressCreateParams] = [] + + @self.server.feature( + TEXT_DOCUMENT_CODE_LENS, + CodeLensOptions(resolve_provider=False, work_done_progress=True), + ) + async def f1(params: CodeLensParams) -> Optional[List[CodeLens]]: + if "client_initiated_token" in params.text_document.uri: + token = params.work_done_token + else: + assert "server_initiated_token" in params.text_document.uri + token = params.text_document.uri[len("file://") :] + if "async" in params.text_document.uri: + await self.server.progress.create_async(token) + else: + f = self.server.progress.create(token) + await asyncio.sleep(0.1) + f.result() + + assert token + self.server.lsp.progress.begin( + token, + WorkDoneProgressBegin(kind="begin", title="starting", percentage=0), + ) + await asyncio.sleep(0.1) + if self.server.lsp.progress.tokens[token].cancelled(): + self.server.lsp.progress.end( + token, WorkDoneProgressEnd(kind="end", message="cancelled") + ) + else: + self.server.lsp.progress.report( + token, + WorkDoneProgressReport( + kind="report", message="doing", percentage=50 + ), + ) + self.server.lsp.progress.end( + token, WorkDoneProgressEnd(kind="end", message="done") + ) + return None + + @self.client.feature(PROGRESS) + def f2(params): + self.client.notifications.append(params) + if params.value["kind"] == "begin" and "cancel" in params.token: + # client cancels the progress token + self.client.lsp.notify( + WINDOW_WORK_DONE_PROGRESS_CANCEL, + WorkDoneProgressCancelParams(token=params.token), + ) + + @self.client.feature(WINDOW_WORK_DONE_PROGRESS_CREATE) + def f3(params: WorkDoneProgressCreateParams): + self.client.method_calls.append(params) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + provider = capabilities.code_lens_provider + assert provider + assert provider.work_done_progress + + +@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.") +@ConfiguredLS.decorate() +async def test_progress_notifications(client_server): + client, _ = client_server + client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams( + text_document=TextDocumentIdentifier(uri="file://client_initiated_token"), + work_done_token="token", + ), + ).result() + + assert [notif.value for notif in client.notifications] == [ + { + "kind": "begin", + "title": "starting", + "percentage": 0, + }, + { + "kind": "report", + "message": "doing", + "percentage": 50, + }, + {"kind": "end", "message": "done"}, + ] + assert {notif.token for notif in client.notifications} == {"token"} + + +@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.") +@pytest.mark.parametrize("registration", ("sync", "async")) +@ConfiguredLS.decorate() +async def test_server_initiated_progress_notifications(client_server, registration): + client, _ = client_server + client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams( + text_document=TextDocumentIdentifier( + uri=f"file://server_initiated_token_{registration}" + ), + work_done_token="token", + ), + ).result() + + assert [notif.value for notif in client.notifications] == [ + { + "kind": "begin", + "title": "starting", + "percentage": 0, + }, + { + "kind": "report", + "message": "doing", + "percentage": 50, + }, + {"kind": "end", "message": "done"}, + ] + assert {notif.token for notif in client.notifications} == { + f"server_initiated_token_{registration}" + } + assert [mc.token for mc in client.method_calls] == [ + f"server_initiated_token_{registration}" + ] + + +@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.") +@ConfiguredLS.decorate() +def test_progress_cancel_notifications(client_server): + client, _ = client_server + client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams( + text_document=TextDocumentIdentifier(uri="file://client_initiated_token"), + work_done_token="token_with_cancellation", + ), + ).result() + assert [notif.value for notif in client.notifications] == [ + { + "kind": "begin", + "title": "starting", + "percentage": 0, + }, + {"kind": "end", "message": "cancelled"}, + ] + assert {notif.token for notif in client.notifications} == { + "token_with_cancellation" + } + + +@pytest.mark.skipif(IS_PYODIDE, reason="threads are not available in pyodide.") +@pytest.mark.parametrize("registration", ("sync", "async")) +@ConfiguredLS.decorate() +def test_server_initiated_progress_progress_cancel_notifications( + client_server, registration +): + client, _ = client_server + client.lsp.send_request( + TEXT_DOCUMENT_CODE_LENS, + CodeLensParams( + text_document=TextDocumentIdentifier( + uri=f"file://server_initiated_token_{registration}_with_cancellation" + ), + ), + ).result() + + assert [notif.value for notif in client.notifications] == [ + { + "kind": "begin", + "title": "starting", + "percentage": 0, + }, + {"kind": "end", "message": "cancelled"}, + ] diff --git a/tests/lsp/test_range_formatting.py b/tests/lsp/test_range_formatting.py new file mode 100644 index 0000000..e7a118c --- /dev/null +++ b/tests/lsp/test_range_formatting.py @@ -0,0 +1,116 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_RANGE_FORMATTING +from lsprotocol.types import ( + DocumentRangeFormattingOptions, + DocumentRangeFormattingParams, + FormattingOptions, + Position, + Range, + TextDocumentIdentifier, + TextEdit, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_RANGE_FORMATTING, + DocumentRangeFormattingOptions(), + ) + def f(params: DocumentRangeFormattingParams) -> Optional[List[TextEdit]]: + if params.text_document.uri == "file://return.list": + return [ + TextEdit( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + new_text="text", + ) + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.document_range_formatting_provider + + +@ConfiguredLS.decorate() +def test_range_formatting_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_RANGE_FORMATTING, + DocumentRangeFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response + + assert response[0].new_text == "text" + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_range_formatting_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_RANGE_FORMATTING, + DocumentRangeFormattingParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + options=FormattingOptions( + tab_size=2, + insert_spaces=True, + trim_trailing_whitespace=True, + insert_final_newline=True, + trim_final_newlines=True, + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_references.py b/tests/lsp/test_references.py new file mode 100644 index 0000000..5867e35 --- /dev/null +++ b/tests/lsp/test_references.py @@ -0,0 +1,103 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_REFERENCES +from lsprotocol.types import ( + Location, + Position, + Range, + ReferenceContext, + ReferenceOptions, + ReferenceParams, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_REFERENCES, + ReferenceOptions(), + ) + def f(params: ReferenceParams) -> Optional[List[Location]]: + if params.text_document.uri == "file://return.list": + return [ + Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ), + ] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.references_provider + + +@ConfiguredLS.decorate() +def test_references_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_REFERENCES, + ReferenceParams( + text_document=TextDocumentIdentifier(uri="file://return.list"), + position=Position(line=0, character=0), + context=ReferenceContext( + include_declaration=True, + ), + ), + ).result() + + assert response + + assert response[0].uri == "uri" + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_references_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_REFERENCES, + ReferenceParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + context=ReferenceContext( + include_declaration=True, + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_rename.py b/tests/lsp/test_rename.py new file mode 100644 index 0000000..48cface --- /dev/null +++ b/tests/lsp/test_rename.py @@ -0,0 +1,195 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Optional + +from lsprotocol.types import TEXT_DOCUMENT_RENAME +from lsprotocol.types import ( + CreateFile, + CreateFileOptions, + DeleteFile, + DeleteFileOptions, + OptionalVersionedTextDocumentIdentifier, + Position, + Range, + RenameFile, + RenameFileOptions, + RenameOptions, + RenameParams, + ResourceOperationKind, + TextDocumentEdit, + TextDocumentIdentifier, + TextEdit, + WorkspaceEdit, +) + +from ..conftest import ClientServer + +workspace_edit = { + "changes": { + "uri1": [ + TextEdit( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + new_text="text1", + ), + TextEdit( + range=Range( + start=Position(line=1, character=1), + end=Position(line=2, character=2), + ), + new_text="text2", + ), + ], + }, + "document_changes": [ + TextDocumentEdit( + text_document=OptionalVersionedTextDocumentIdentifier( + uri="uri", + version=3, + ), + edits=[ + TextEdit( + range=Range( + start=Position(line=2, character=2), + end=Position(line=3, character=3), + ), + new_text="text3", + ), + ], + ), + CreateFile( + kind=ResourceOperationKind.Create.value, + uri="create file", + options=CreateFileOptions( + overwrite=True, + ignore_if_exists=True, + ), + ), + RenameFile( + kind=ResourceOperationKind.Rename.value, + old_uri="rename old uri", + new_uri="rename new uri", + options=RenameFileOptions( + overwrite=True, + ignore_if_exists=True, + ), + ), + DeleteFile( + kind=ResourceOperationKind.Delete.value, + uri="delete file", + options=DeleteFileOptions( + recursive=True, + ignore_if_not_exists=True, + ), + ), + ], +} + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_RENAME, + RenameOptions(prepare_provider=True), + ) + def f(params: RenameParams) -> Optional[WorkspaceEdit]: + if params.text_document.uri == "file://return.workspace_edit": + return WorkspaceEdit(**workspace_edit) + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.rename_provider + assert capabilities.rename_provider.prepare_provider + + +@ConfiguredLS.decorate() +def test_rename_return_workspace_edit(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_RENAME, + RenameParams( + text_document=TextDocumentIdentifier(uri="file://return.workspace_edit"), + position=Position(line=0, character=0), + new_name="new name", + ), + ).result() + + assert response + + changes = response.changes["uri1"] + assert changes[0].new_text == "text1" + assert changes[0].range.start.line == 0 + assert changes[0].range.start.character == 0 + assert changes[0].range.end.line == 1 + assert changes[0].range.end.character == 1 + + assert changes[1].new_text == "text2" + assert changes[1].range.start.line == 1 + assert changes[1].range.start.character == 1 + assert changes[1].range.end.line == 2 + assert changes[1].range.end.character == 2 + + changes = response.document_changes + assert changes[0].text_document.uri == "uri" + assert changes[0].text_document.version == 3 + assert changes[0].edits[0].new_text == "text3" + assert changes[0].edits[0].range.start.line == 2 + assert changes[0].edits[0].range.start.character == 2 + assert changes[0].edits[0].range.end.line == 3 + assert changes[0].edits[0].range.end.character == 3 + + assert changes[1].kind == ResourceOperationKind.Create.value + assert changes[1].uri == "create file" + assert changes[1].options.ignore_if_exists + assert changes[1].options.overwrite + + assert changes[2].kind == ResourceOperationKind.Rename.value + assert changes[2].new_uri == "rename new uri" + assert changes[2].old_uri == "rename old uri" + assert changes[2].options.ignore_if_exists + assert changes[2].options.overwrite + + assert changes[3].kind == ResourceOperationKind.Delete.value + assert changes[3].uri == "delete file" + assert changes[3].options.ignore_if_not_exists + assert changes[3].options.recursive + + +@ConfiguredLS.decorate() +def test_rename_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_RENAME, + RenameParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + new_name="new name", + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_selection_range.py b/tests/lsp/test_selection_range.py new file mode 100644 index 0000000..5f669a2 --- /dev/null +++ b/tests/lsp/test_selection_range.py @@ -0,0 +1,110 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import List, Optional + +from lsprotocol.types import TEXT_DOCUMENT_SELECTION_RANGE +from lsprotocol.types import ( + Position, + Range, + SelectionRange, + SelectionRangeOptions, + SelectionRangeParams, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_SELECTION_RANGE, + SelectionRangeOptions(), + ) + def f(params: SelectionRangeParams) -> Optional[List[SelectionRange]]: + if params.text_document.uri == "file://return.list": + root = SelectionRange( + range=Range( + start=Position(line=0, character=0), + end=Position(line=10, character=10), + ), + ) + + inner_range = SelectionRange( + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + parent=root, + ) + + return [root, inner_range] + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.selection_range_provider + + +@ConfiguredLS.decorate() +def test_selection_range_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SELECTION_RANGE, + SelectionRangeParams( + # query="query", + text_document=TextDocumentIdentifier(uri="file://return.list"), + positions=[Position(line=0, character=0)], + ), + ).result() + + assert response + + root = response[0] + assert root.range.start.line == 0 + assert root.range.start.character == 0 + assert root.range.end.line == 10 + assert root.range.end.character == 10 + assert root.parent is None + + assert response[1].range.start.line == 0 + assert response[1].range.start.character == 0 + assert response[1].range.end.line == 1 + assert response[1].range.end.character == 1 + assert response[1].parent == root + + +@ConfiguredLS.decorate() +def test_selection_range_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SELECTION_RANGE, + SelectionRangeParams( + # query="query", + text_document=TextDocumentIdentifier(uri="file://return.none"), + positions=[Position(line=0, character=0)], + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_signature_help.py b/tests/lsp/test_signature_help.py new file mode 100644 index 0000000..e318120 --- /dev/null +++ b/tests/lsp/test_signature_help.py @@ -0,0 +1,161 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ + +from typing import Optional + +import pytest + +from lsprotocol.types import TEXT_DOCUMENT_SIGNATURE_HELP +from lsprotocol.types import ( + ParameterInformation, + Position, + SignatureHelp, + SignatureHelpContext, + SignatureHelpOptions, + SignatureHelpParams, + SignatureHelpTriggerKind, + SignatureInformation, + TextDocumentIdentifier, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_SIGNATURE_HELP, + SignatureHelpOptions( + trigger_characters=["a", "b"], + retrigger_characters=["c", "d"], + ), + ) + def f(params: SignatureHelpParams) -> Optional[SignatureHelp]: + if params.text_document.uri == "file://return.signature_help": + return SignatureHelp( + signatures=[ + SignatureInformation( + label="label", + documentation="documentation", + parameters=[ + ParameterInformation( + label=(0, 0), + documentation="documentation", + ), + ], + ), + ], + active_signature=0, + active_parameter=0, + ) + else: + return None + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + provider = capabilities.signature_help_provider + assert provider + assert provider.trigger_characters == ["a", "b"] + assert provider.retrigger_characters == ["c", "d"] + + +@ConfiguredLS.decorate() +@pytest.mark.skip +def test_signature_help_return_signature_help(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SIGNATURE_HELP, + SignatureHelpParams( + text_document=TextDocumentIdentifier(uri="file://return.signature_help"), + position=Position(line=0, character=0), + context=SignatureHelpContext( + trigger_kind=SignatureHelpTriggerKind.TriggerCharacter, + is_retrigger=True, + trigger_character="a", + active_signature_help=SignatureHelp( + signatures=[ + SignatureInformation( + label="label", + documentation="documentation", + parameters=[ + ParameterInformation( + label=(0, 0), + documentation="documentation", + ), + ], + ), + ], + active_signature=0, + active_parameter=0, + ), + ), + ), + ).result() + + assert response + + assert response["activeParameter"] == 0 + assert response["activeSignature"] == 0 + + assert response["signatures"][0]["label"] == "label" + assert response["signatures"][0]["documentation"] == "documentation" + assert response["signatures"][0]["parameters"][0]["label"] == [0, 0] + assert ( + response["signatures"][0]["parameters"][0]["documentation"] == "documentation" + ) + + +@ConfiguredLS.decorate() +@pytest.mark.skip +def test_signature_help_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_SIGNATURE_HELP, + SignatureHelpParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + context=SignatureHelpContext( + trigger_kind=SignatureHelpTriggerKind.TriggerCharacter, + is_retrigger=True, + trigger_character="a", + active_signature_help=SignatureHelp( + signatures=[ + SignatureInformation( + label="label", + documentation="documentation", + parameters=[ + ParameterInformation( + label=(0, 0), + documentation="documentation", + ), + ], + ), + ], + active_signature=0, + active_parameter=0, + ), + ), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_type_definition.py b/tests/lsp/test_type_definition.py new file mode 100644 index 0000000..b6d3eff --- /dev/null +++ b/tests/lsp/test_type_definition.py @@ -0,0 +1,163 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import List, Optional, Union + +from lsprotocol.types import TEXT_DOCUMENT_TYPE_DEFINITION +from lsprotocol.types import ( + Location, + LocationLink, + Position, + Range, + TextDocumentIdentifier, + TypeDefinitionOptions, + TypeDefinitionParams, +) + +from ..conftest import ClientServer + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature( + TEXT_DOCUMENT_TYPE_DEFINITION, + TypeDefinitionOptions(), + ) + def f( + params: TypeDefinitionParams, + ) -> Optional[Union[Location, List[Location], List[LocationLink]]]: + location = Location( + uri="uri", + range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + ) + + location_link = LocationLink( + target_uri="uri", + target_range=Range( + start=Position(line=0, character=0), + end=Position(line=1, character=1), + ), + target_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=2, character=2), + ), + origin_selection_range=Range( + start=Position(line=0, character=0), + end=Position(line=3, character=3), + ), + ) + + return { # type: ignore + "file://return.location": location, + "file://return.location_list": [location], + "file://return.location_link_list": [location_link], + }.get(params.text_document.uri, None) + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + + assert capabilities.type_definition_provider + + +@ConfiguredLS.decorate() +def test_type_definition_return_location(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_TYPE_DEFINITION, + TypeDefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.location"), + position=Position(line=0, character=0), + ), + ).result() + + assert response.uri == "uri" + + assert response.range.start.line == 0 + assert response.range.start.character == 0 + assert response.range.end.line == 1 + assert response.range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_type_definition_return_location_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_TYPE_DEFINITION, + TypeDefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.location_list"), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].uri == "uri" + + assert response[0].range.start.line == 0 + assert response[0].range.start.character == 0 + assert response[0].range.end.line == 1 + assert response[0].range.end.character == 1 + + +@ConfiguredLS.decorate() +def test_type_definition_return_location_link_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_TYPE_DEFINITION, + TypeDefinitionParams( + text_document=TextDocumentIdentifier( + uri="file://return.location_link_list" + ), + position=Position(line=0, character=0), + ), + ).result() + + assert response[0].target_uri == "uri" + + assert response[0].target_range.start.line == 0 + assert response[0].target_range.start.character == 0 + assert response[0].target_range.end.line == 1 + assert response[0].target_range.end.character == 1 + + assert response[0].target_selection_range.start.line == 0 + assert response[0].target_selection_range.start.character == 0 + assert response[0].target_selection_range.end.line == 2 + assert response[0].target_selection_range.end.character == 2 + + assert response[0].origin_selection_range.start.line == 0 + assert response[0].origin_selection_range.start.character == 0 + assert response[0].origin_selection_range.end.line == 3 + assert response[0].origin_selection_range.end.character == 3 + + +@ConfiguredLS.decorate() +def test_type_definition_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + TEXT_DOCUMENT_TYPE_DEFINITION, + TypeDefinitionParams( + text_document=TextDocumentIdentifier(uri="file://return.none"), + position=Position(line=0, character=0), + ), + ).result() + + assert response is None diff --git a/tests/lsp/test_type_hierarchy.py b/tests/lsp/test_type_hierarchy.py new file mode 100644 index 0000000..e186c7f --- /dev/null +++ b/tests/lsp/test_type_hierarchy.py @@ -0,0 +1,127 @@ +############################################################################ +# Copyright(c) Open Law Library. All rights reserved. # +# See ThirdPartyNotices.txt in the project root for additional notices. # +# # +# Licensed under the Apache License, Version 2.0 (the "License") # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http: // www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################ +from typing import List, Optional + +from lsprotocol import types as lsp + +from ..conftest import ClientServer + + +TYPE_HIERARCHY_ITEM = lsp.TypeHierarchyItem( + name="test_name", + kind=lsp.SymbolKind.Class, + uri="test_uri", + range=lsp.Range( + start=lsp.Position(line=0, character=0), + end=lsp.Position(line=0, character=6), + ), + selection_range=lsp.Range( + start=lsp.Position(line=0, character=0), + end=lsp.Position(line=0, character=6), + ), +) + + +def check_type_hierarchy_item_response(item): + assert item.name == TYPE_HIERARCHY_ITEM.name + assert item.kind == TYPE_HIERARCHY_ITEM.kind + assert item.uri == TYPE_HIERARCHY_ITEM.uri + assert item.range == TYPE_HIERARCHY_ITEM.range + assert item.selection_range == TYPE_HIERARCHY_ITEM.selection_range + + +class ConfiguredLS(ClientServer): + def __init__(self): + super().__init__() + + @self.server.feature(lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY) + def f1( + params: lsp.TypeHierarchyPrepareParams, + ) -> Optional[List[lsp.TypeHierarchyItem]]: + if params.text_document.uri == "file://return.list": + return [TYPE_HIERARCHY_ITEM] + else: + return None + + @self.server.feature(lsp.TYPE_HIERARCHY_SUPERTYPES) + def f2( + params: lsp.TypeHierarchySupertypesParams, + ) -> Optional[List[lsp.TypeHierarchyItem]]: + return [TYPE_HIERARCHY_ITEM] + + @self.server.feature(lsp.TYPE_HIERARCHY_SUBTYPES) + def f3( + params: lsp.TypeHierarchySubtypesParams, + ) -> Optional[List[lsp.TypeHierarchyItem]]: + return [TYPE_HIERARCHY_ITEM] + + +@ConfiguredLS.decorate() +def test_capabilities(client_server): + _, server = client_server + capabilities = server.server_capabilities + assert capabilities.type_hierarchy_provider + + +@ConfiguredLS.decorate() +def test_type_hierarchy_prepare_return_list(client_server): + client, _ = client_server + response = client.lsp.send_request( + lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, + lsp.TypeHierarchyPrepareParams( + text_document=lsp.TextDocumentIdentifier(uri="file://return.list"), + position=lsp.Position(line=0, character=0), + ), + ).result() + + check_type_hierarchy_item_response(response[0]) + + +@ConfiguredLS.decorate() +def test_type_hierarchy_prepare_return_none(client_server): + client, _ = client_server + response = client.lsp.send_request( + lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, + lsp.TypeHierarchyPrepareParams( + text_document=lsp.TextDocumentIdentifier(uri="file://return.none"), + position=lsp.Position(line=0, character=0), + ), + ).result() + + assert response is None + + +@ConfiguredLS.decorate() +def test_type_hierarchy_supertypes(client_server): + client, _ = client_server + response = client.lsp.send_request( + lsp.TYPE_HIERARCHY_SUPERTYPES, + lsp.TypeHierarchySupertypesParams(item=TYPE_HIERARCHY_ITEM), + ).result() + + check_type_hierarchy_item_response(response[0]) + + +@ConfiguredLS.decorate() +def test_type_hierarchy_subtypes(client_server): + client, _ = client_server + response = client.lsp.send_request( + lsp.TYPE_HIERARCHY_SUBTYPES, + lsp.TypeHierarchySubtypesParams(item=TYPE_HIERARCHY_ITEM), + ).result() + + check_type_hierarchy_item_response(response[0]) |