From c44352c3ac15cb75c7d58d4601855207eac1e5af Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 11 Nov 2020 23:51:28 +0100 Subject: [PATCH 1/2] Use maxOccurs to limit tag suggestion --- server/galaxyls/services/completion.py | 13 ++++++++++--- server/galaxyls/services/context.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/server/galaxyls/services/completion.py b/server/galaxyls/services/completion.py index f4f2e3d..03982f2 100644 --- a/server/galaxyls/services/completion.py +++ b/server/galaxyls/services/completion.py @@ -39,7 +39,7 @@ def get_completion_at_context( elif triggerKind == CompletionTriggerKind.Invoked: if context.is_attribute_value: return self.get_attribute_value_completion(context) - if context.is_tag: + if context.is_tag and not context.is_closing_tag: if context.token.name: return self.get_attribute_completion(context) return self.get_node_completion(context) @@ -62,7 +62,8 @@ def get_node_completion(self, context: XmlContext) -> CompletionList: result.append(self._build_node_completion_item(context.xsd_element)) elif context.xsd_element: for child in context.xsd_element.children: - result.append(self._build_node_completion_item(child, len(result))) + if not context.has_reached_max_occurs(child): + result.append(self._build_node_completion_item(child, len(result))) result.append(self._build_node_completion_item(self.xsd_tree.expand_element, len(result))) return CompletionList(items=result, is_incomplete=False) @@ -78,7 +79,13 @@ def get_attribute_completion(self, context: XmlContext) -> CompletionList: CompletionList: The completion item with the basic information about the attributes. """ - if context.is_empty or context.is_content or context.is_attribute_value or context.is_closing_tag: + if ( + context.is_empty + or context.is_content + or context.is_attribute_value + or context.is_closing_tag + or not context.token.name + ): return CompletionList(is_incomplete=False) result = [] diff --git a/server/galaxyls/services/context.py b/server/galaxyls/services/context.py index 106cdb3..a96f7e9 100644 --- a/server/galaxyls/services/context.py +++ b/server/galaxyls/services/context.py @@ -107,6 +107,25 @@ def stack(self) -> List[str]: return [] return self._node.stack + def has_reached_max_occurs(self, node: XsdNode) -> bool: + """Checks if the given node has reached the maximum number + of ocurrences. + + Args: + child (XsdNode): The node to check. + + Returns: + bool: True if the node has reached the maximum number + of ocurrences permitted. + """ + if node.max_occurs < 0: + return False + parent = self._node.parent + if parent: + existing_count = sum(1 for child_node in parent.children if child_node.name == node.name) + return existing_count >= node.max_occurs + return False + class XmlContextService: """This service provides information about the XML context at From adc226cbecba8c36cc1090160e2b6da06999f7c7 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Thu, 12 Nov 2020 18:45:39 +0100 Subject: [PATCH 2/2] Add tests for tag maxOccurs limitation + small fixes --- server/galaxyls/services/context.py | 10 +++--- server/galaxyls/tests/unit/test_completion.py | 31 ++++++++++++++----- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/server/galaxyls/services/context.py b/server/galaxyls/services/context.py index a96f7e9..5ab3acc 100644 --- a/server/galaxyls/services/context.py +++ b/server/galaxyls/services/context.py @@ -66,7 +66,7 @@ def is_empty(self) -> bool: @property def is_root(self) -> bool: """Indicates if the element at context is the root element.""" - return self._node and len(self._node.ancestors) == 1 + return self._node is not None and len(self._node.ancestors) == 1 @property def is_tag(self) -> bool: @@ -86,7 +86,7 @@ def is_attribute_value(self) -> bool: @property def attribute_name(self) -> Optional[str]: """The name of the attribute if the context is an attribute or None.""" - return self._node is not None and self._node.get_attribute_name() + return self._node and self._node.get_attribute_name() @property def is_content(self) -> bool: @@ -120,9 +120,9 @@ def has_reached_max_occurs(self, node: XsdNode) -> bool: """ if node.max_occurs < 0: return False - parent = self._node.parent - if parent: - existing_count = sum(1 for child_node in parent.children if child_node.name == node.name) + target = self._node.parent or self._node + if target: + existing_count = sum(1 for child_node in target.children if child_node.name == node.name) return existing_count >= node.max_occurs return False diff --git a/server/galaxyls/tests/unit/test_completion.py b/server/galaxyls/tests/unit/test_completion.py index 6b418e1..17152b1 100644 --- a/server/galaxyls/tests/unit/test_completion.py +++ b/server/galaxyls/tests/unit/test_completion.py @@ -26,7 +26,8 @@ def fake_tree(mocker: MockerFixture) -> XsdTree: fake_attr = XsdAttribute("attr", element=mocker.Mock()) fake_attr.enumeration = ["v1", "v2"] fake_root.attributes[fake_attr.name] = fake_attr - XsdNode("child", element=mocker.Mock(), parent=fake_root) + child = XsdNode("child", element=mocker.Mock(), parent=fake_root) + child.max_occurs = 1 return XsdTree(fake_root) @@ -85,7 +86,7 @@ def test_get_completion_at_context_with_open_tag_trigger_returns_expected_node(s assert actual.items[1].label == "expand" assert actual.items[1].kind == CompletionItemKind.Class - def test_get_completion_at_context_with_open_tag_invoke_returns_expected_node(self, fake_tree: XsdTree) -> None: + def test_get_completion_at_context_with_closing_tag_invoke_returns_empty(self, fake_tree: XsdTree) -> None: fake_element = XmlElement() fake_context = XmlContext(fake_tree.root, fake_element) fake_completion_context = CompletionContext(CompletionTriggerKind.Invoked) @@ -94,14 +95,11 @@ def test_get_completion_at_context_with_open_tag_invoke_returns_expected_node(se actual = service.get_completion_at_context(fake_context, fake_completion_context) assert actual - assert len(actual.items) == 2 - assert actual.items[0].label == "child" - assert actual.items[0].kind == CompletionItemKind.Class - assert actual.items[1].label == "expand" - assert actual.items[1].kind == CompletionItemKind.Class + assert len(actual.items) == 0 def test_get_completion_at_context_on_node_returns_expected_attributes(self, fake_tree: XsdTree) -> None: fake_node = XmlElement() + fake_node.name = "root" fake_node.end_tag_open_offset = 10 fake_node.end_tag_close_offset = 12 fake_context = XmlContext(fake_tree.root, fake_node) @@ -119,6 +117,7 @@ def test_get_completion_at_context_on_node_with_attribute_returns_expected_attri self, fake_tree_with_attrs: XsdTree ) -> None: fake_node = XmlElement() + fake_node.name = "root" fake_node.end_tag_open_offset = 10 fake_node.end_tag_close_offset = 12 fake_node.attributes["one"] = XmlAttribute("one", 0, 0, fake_node) @@ -160,6 +159,24 @@ def test_return_valid_completion_with_node_context(self, fake_tree: XsdTree, fak assert actual.items[0].label == fake_tree.root.children[0].name assert actual.items[1].label == "expand" + def test_completion_node_reached_max_occurs_return_expected(self, fake_tree: XsdTree, mocker: MockerFixture) -> None: + fake_root = XmlElement() + fake_root.name = fake_tree.root.name + fake_root.end_tag_open_offset = 10 + fake_root.end_tag_close_offset = 15 + fake_child = XmlElement() + fake_child.name = "child" + fake_child.parent = fake_root + fake_child = XmlElement() + fake_child.parent = fake_root + fake_context = XmlContext(fake_tree.root, fake_root) + service = XmlCompletionService(fake_tree) + + actual = service.get_node_completion(fake_context) + + assert len(actual.items) == 1 + assert actual.items[0].label == "expand" + def test_completion_return_root_node_when_empty_context(self, fake_tree: XsdTree, fake_empty_context) -> None: service = XmlCompletionService(fake_tree)