diff --git a/noxfile.py b/noxfile.py index 4717e0d..88cf122 100644 --- a/noxfile.py +++ b/noxfile.py @@ -23,6 +23,9 @@ def tests(session: nox.Session): """Run tests for generator and generated code in all languages.""" _install_requirements(session) + session.log("Running test data generator.") + session.run("python", "-m", "generator", "--plugin", "testdata") + session.log("Running tests: generator and generated Python code.") session.run("pytest", "./tests") diff --git a/packages/python/lsprotocol/_hooks.py b/packages/python/lsprotocol/_hooks.py index dbd51f9..06ade48 100644 --- a/packages/python/lsprotocol/_hooks.py +++ b/packages/python/lsprotocol/_hooks.py @@ -634,6 +634,88 @@ def _notebook_sync_option_selector_hook( object_, lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2 ) + def _semantic_token_registration_options_hook( + object_: Any, _: type + ) -> Optional[ + Union[OptionalPrimitive, lsp_types.SemanticTokensRegistrationOptionsFullType1] + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure( + object_, lsp_types.SemanticTokensRegistrationOptionsFullType1 + ) + + def _inline_completion_provider_hook( + object_: Any, _: type + ) -> Optional[Union[OptionalPrimitive, lsp_types.InlineCompletionOptions]]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.InlineCompletionOptions) + + def _inline_completion_list_hook( + object_: Any, _: type + ) -> Optional[ + Union[lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem]] + ]: + if object_ is None: + return None + if isinstance(object_, list): + return [ + converter.structure(item, lsp_types.InlineCompletionItem) + for item in object_ + ] + return converter.structure(object_, lsp_types.InlineCompletionList) + + def _string_value_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.StringValue]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.StringValue) + + def _symbol_list_hook( + object_: Any, _: type + ) -> Optional[ + Union[List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol]] + ]: + if object_ is None: + return None + assert isinstance(object_, list) + if len(object_) == 0: + return [] # type: ignore[return-value] + if "location" in object_[0]: + return [ + converter.structure(item, lsp_types.SymbolInformation) + for item in object_ + ] + else: + return [ + converter.structure(item, lsp_types.WorkspaceSymbol) for item in object_ + ] + + def _notebook_sync_registration_option_selector_hook( + object_: Any, _: type + ) -> Union[ + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ]: + if "notebook" in object_: + return converter.structure( + object_, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + ) + else: + return converter.structure( + object_, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ) + structure_hooks = [ ( Optional[ @@ -940,6 +1022,41 @@ def _notebook_sync_option_selector_hook( ], _position_encoding_hook, ), + ( + Optional[Union[bool, lsp_types.SemanticTokensRegistrationOptionsFullType1]], + _semantic_token_registration_options_hook, + ), + ( + Optional[Union[bool, lsp_types.InlineCompletionOptions]], + _inline_completion_provider_hook, + ), + ( + Optional[ + Union[ + lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem] + ] + ], + _inline_completion_list_hook, + ), + ( + Union[str, lsp_types.StringValue], + _string_value_hook, + ), + ( + Optional[ + Union[ + List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol] + ] + ], + _symbol_list_hook, + ), + ( + Union[ + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ], + _notebook_sync_registration_option_selector_hook, + ), ] for type_, hook in structure_hooks: converter.register_structure_hook(type_, hook) diff --git a/tests/python/test_generated_data.py b/tests/python/test_generated_data.py new file mode 100644 index 0000000..0ecee3c --- /dev/null +++ b/tests/python/test_generated_data.py @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import pathlib +from typing import Generator, List, Union + +import pytest + +import lsprotocol.converters as cv +import lsprotocol.types as lsp + +TEST_DATA_ROOT = pathlib.Path(__file__).parent.parent.parent / "packages" / "testdata" + + +def get_all_json_files(root: Union[pathlib.Path, str]) -> List[pathlib.Path]: + root_path = pathlib.Path(root) + return list(root_path.glob("**/*.json")) + + +converter = cv.get_converter() + + +@pytest.mark.parametrize("json_file", get_all_json_files(TEST_DATA_ROOT)) +def test_generated_data(json_file: str) -> None: + type_name, result_type, _ = json_file.name.split("-", 2) + lsp_type = getattr(lsp, type_name) + data = json.loads(json_file.read_text(encoding="utf-8")) + + try: + converter.structure(data, lsp_type) + assert result_type == "True", "Expected error, but succeeded structuring" + except Exception as e: + assert result_type == "False", "Expected success, but failed structuring"