diff --git a/src/cfengine_cli/format.py b/src/cfengine_cli/format.py index 46744b7..192a03e 100644 --- a/src/cfengine_cli/format.py +++ b/src/cfengine_cli/format.py @@ -280,7 +280,7 @@ def maybe_split_rval( # --------------------------------------------------------------------------- -def attempt_split_attribute(node: Node, indent: int, line_length: int) -> list[str]: +def _attempt_split_attribute(node: Node, indent: int, line_length: int) -> list[str]: """Split an attribute node, wrapping the rval if it's a list or call.""" assert len(node.children) >= 3 # lval + arrow + rval + optionally comments @@ -311,7 +311,7 @@ def attempt_split_attribute(node: Node, indent: int, line_length: int) -> list[s return comment_lines + [" " * indent + stringify_single_line_node(node)] -def stringify(node: Node, indent: int, line_length: int) -> list[str]: +def _stringify(node: Node, indent: int, line_length: int) -> list[str]: """Return a node as pre-indented line(s), splitting if it exceeds line_length.""" single_line = " " * indent + stringify_single_line_node(node) # Reserve 1 char for trailing ; or , after attributes @@ -319,7 +319,7 @@ def stringify(node: Node, indent: int, line_length: int) -> list[str]: if len(single_line) < effective_length: return [single_line] if node.type == "attribute": - return attempt_split_attribute(node, indent, line_length - 1) + return _attempt_split_attribute(node, indent, line_length - 1) return [single_line] @@ -447,7 +447,7 @@ def _has_stakeholder(children: list[Node]) -> bool: return any(c.type == "stakeholder" for c in children) -def can_single_line_promise(node: Node, indent: int, line_length: int) -> bool: +def _can_single_line_promise(node: Node, indent: int, line_length: int) -> bool: """Check if a promise can be formatted entirely on one line. Returns False for multi-attribute promises, promises with a @@ -514,7 +514,7 @@ def _format_promise( ) -> bool: """Format a promise node. Returns True if handled, False to fall through.""" # Single-line promise - if can_single_line_promise(node, indent, line_length): + if _can_single_line_promise(node, indent, line_length): prefix = _promiser_line_with_stakeholder(children) assert prefix is not None attr = next((c for c in children if c.type == "attribute"), None) @@ -569,7 +569,7 @@ def _format_remaining_children( for child in children: if child.type in PROMISER_PARTS: continue - autoformat(child, fmt, line_length, indent) + _autoformat(child, fmt, line_length, indent) # --------------------------------------------------------------------------- @@ -645,8 +645,8 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool promise_indent = indent + 2 both_single = ( prev_content.type == "promise" - and can_single_line_promise(prev_content, promise_indent, line_length) - and can_single_line_promise(child, promise_indent, line_length) + and _can_single_line_promise(prev_content, promise_indent, line_length) + and _can_single_line_promise(child, promise_indent, line_length) ) return not both_single @@ -708,7 +708,7 @@ def _comment_indent(node: Node, indent: int) -> int: # --------------------------------------------------------------------------- -def autoformat( +def _autoformat( node: Node, fmt: Formatter, line_length: int, @@ -744,7 +744,7 @@ def autoformat( # Attribute — stringify and return if node.type == "attribute": - fmt.print_lines(stringify(node, indent, line_length), indent=0) + fmt.print_lines(_stringify(node, indent, line_length), indent=0) return # Promise — delegate to promise formatter @@ -757,7 +757,7 @@ def autoformat( for child in children: if _needs_blank_line_before(child, indent, line_length): fmt.blank_line() - autoformat(child, fmt, line_length, indent) + _autoformat(child, fmt, line_length, indent) return # Leaf nodes @@ -796,7 +796,7 @@ def format_policy_file(filename: str, line_length: int, check: bool) -> int: check_policy_syntax(tree, filename) fmt = Formatter() - autoformat(root_node, fmt, line_length) + _autoformat(root_node, fmt, line_length) new_data = fmt.buffer + "\n" if new_data != original_data.decode("utf-8"): @@ -828,7 +828,7 @@ def format_policy_fin_fout( check_policy_syntax(tree, "") fmt = Formatter() - autoformat(root_node, fmt, line_length) + _autoformat(root_node, fmt, line_length) new_data = fmt.buffer + "\n" fout.write(new_data) diff --git a/tests/unit/test_format.py b/tests/unit/test_format.py index 60e38a0..5b1f4b5 100644 --- a/tests/unit/test_format.py +++ b/tests/unit/test_format.py @@ -16,23 +16,7 @@ split_rval, maybe_split_rval, split_generic_value, - attempt_split_attribute, - stringify, - can_single_line_promise, - autoformat, format_policy_fin_fout, - _get_stakeholder_list, - _stakeholder_has_comments, - _has_trailing_comma, - _promiser_text, - _promiser_line_with_stakeholder, - _stakeholder_needs_splitting, - _format_stakeholder_elements, - _has_stakeholder, - _is_empty_comment, - _skip_comments, - _comment_indent, - _needs_blank_line_before, ) # --------------------------------------------------------------------------- @@ -400,349 +384,6 @@ def test_split_generic_value_other(): assert result == ['"hello"'] -# --------------------------------------------------------------------------- -# attempt_split_attribute / stringify -# --------------------------------------------------------------------------- - - -def test_attempt_split_attribute_with_list(): - root = _parse('bundle agent x { vars: "v" slist => { "a", "b" }; }') - attr = _find(root, "attribute") - result = attempt_split_attribute(attr, 6, 20) - assert len(result) > 1 - assert "slist => {" in result[0] - - -def test_attempt_split_attribute_with_string(): - root = _parse('bundle agent x { vars: "v" string => "hello"; }') - attr = _find(root, "attribute") - result = attempt_split_attribute(attr, 6, 80) - assert len(result) == 1 - assert 'string => "hello"' in result[0] - - -def test_stringify_short_attribute(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - attr = _find(root, "attribute") - result = stringify(attr, 6, 80) - assert len(result) == 1 - assert result[0] == ' string => "hi"' - - -def test_stringify_long_attribute_splits(): - root = _parse('bundle agent x { vars: "v" slist => { "aaa", "bbb" }; }') - attr = _find(root, "attribute") - result = stringify(attr, 6, 30) - assert len(result) > 1 - - -def test_stringify_non_attribute(): - node = _leaf("identifier", "hello") - result = stringify(node, 4, 80) - assert result == [" hello"] - - -# --------------------------------------------------------------------------- -# Stakeholder helpers -# --------------------------------------------------------------------------- - - -def test_get_stakeholder_list_present(): - root = _parse('bundle agent x { packages: "p" -> { "a", "b" } comment => "c"; }') - promise = _find(root, "promise") - list_node = _get_stakeholder_list(promise.children) - assert list_node is not None - assert list_node.type == "list" - - -def test_get_stakeholder_list_absent(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - promise = _find(root, "promise") - assert _get_stakeholder_list(promise.children) is None - - -def test_stakeholder_has_comments(): - root = _parse( - 'bundle agent x { packages: "p" -> {\n# comment\n"a" } comment => "c"; }' - ) - promise = _find(root, "promise") - assert _stakeholder_has_comments(promise.children) is True - - -def test_stakeholder_no_comments(): - root = _parse('bundle agent x { packages: "p" -> { "a", "b" } comment => "c"; }') - promise = _find(root, "promise") - assert _stakeholder_has_comments(promise.children) is False - - -def test_has_trailing_comma_true(): - nodes = [_leaf("string", '"a"'), _leaf(","), _leaf("string", '"b"'), _leaf(",")] - assert _has_trailing_comma(nodes) is True - - -def test_has_trailing_comma_false(): - nodes = [_leaf("string", '"a"'), _leaf(","), _leaf("string", '"b"')] - assert _has_trailing_comma(nodes) is False - - -def test_has_trailing_comma_empty(): - assert _has_trailing_comma([]) is False - - -def test_has_trailing_comma_comment_after(): - nodes = [_leaf("string", '"a"'), _leaf(","), _leaf("comment", "# x")] - assert _has_trailing_comma(nodes) is True - - -def test_promiser_text(): - root = _parse('bundle agent x { vars: "myvar" string => "hi"; }') - promise = _find(root, "promise") - assert _promiser_text(promise.children) == '"myvar"' - - -def test_promiser_text_absent(): - assert _promiser_text([_leaf("attribute", "x")]) is None - - -def test_promiser_line_with_stakeholder(): - root = _parse('bundle agent x { packages: "p" -> { "a", "b" } comment => "c"; }') - promise = _find(root, "promise") - line = _promiser_line_with_stakeholder(promise.children) - assert line is not None - assert line.startswith('"p"') - assert "-> { " in line - - -def test_promiser_line_without_stakeholder(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - promise = _find(root, "promise") - line = _promiser_line_with_stakeholder(promise.children) - assert line == '"v"' - - -def test_stakeholder_needs_splitting_with_comments(): - root = _parse( - 'bundle agent x { packages: "p" -> {\n# comment\n"a" } comment => "c"; }' - ) - promise = _find(root, "promise") - assert _stakeholder_needs_splitting(promise.children, 4, 80) is True - - -def test_stakeholder_needs_splitting_long_line(): - root = _parse( - 'bundle agent x { packages: "long_package" -> { "very long reason", "TICKET-1234" } comment => "c"; }' - ) - promise = _find(root, "promise") - assert _stakeholder_needs_splitting(promise.children, 4, 40) is True - - -def test_stakeholder_no_splitting_needed(): - root = _parse('bundle agent x { packages: "p" -> { "a" } comment => "c"; }') - promise = _find(root, "promise") - assert _stakeholder_needs_splitting(promise.children, 4, 80) is False - - -def test_has_stakeholder_true(): - root = _parse('bundle agent x { packages: "p" -> { "a" } comment => "c"; }') - promise = _find(root, "promise") - assert _has_stakeholder(promise.children) is True - - -def test_has_stakeholder_false(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - promise = _find(root, "promise") - assert _has_stakeholder(promise.children) is False - - -def test_format_stakeholder_elements_no_trailing_comma(): - nodes = [_leaf("string", '"a"'), _leaf(","), _leaf("string", '"b"')] - result = _format_stakeholder_elements(nodes, 8, 80) - assert len(result) == 1 - assert '"a", "b"' in result[0] - - -def test_format_stakeholder_elements_trailing_comma(): - nodes = [ - _leaf("string", '"a"'), - _leaf(","), - _leaf("string", '"b"'), - _leaf(","), - ] - result = _format_stakeholder_elements(nodes, 8, 80) - assert len(result) == 2 - - -def test_format_stakeholder_elements_with_comments(): - nodes = [ - _leaf("comment", "# note"), - _leaf("string", '"a"'), - _leaf(","), - _leaf("string", '"b"'), - ] - result = _format_stakeholder_elements(nodes, 8, 80) - assert any("# note" in line for line in result) - assert any('"a"' in line for line in result) - - -# --------------------------------------------------------------------------- -# can_single_line_promise -# --------------------------------------------------------------------------- - - -def test_can_single_line_promise_simple(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - promise = _find(root, "promise") - assert can_single_line_promise(promise, 4, 80) is True - - -def test_can_single_line_promise_too_long(): - root = _parse('bundle agent x { vars: "v" string => "hi"; }') - promise = _find(root, "promise") - assert can_single_line_promise(promise, 4, 10) is False - - -def test_can_single_line_promise_multi_attr(): - root = _parse('bundle agent x { vars: "v" if => "linux", string => "hi"; }') - promise = _find(root, "promise") - assert can_single_line_promise(promise, 4, 80) is False - - -def test_can_single_line_promise_with_stakeholder_and_attr(): - root = _parse('bundle agent x { packages: "p" -> { "a" } comment => "c"; }') - promise = _find(root, "promise") - assert can_single_line_promise(promise, 4, 200) is False - - -def test_can_single_line_promise_bare_promiser(): - root = _parse('bundle agent x { packages: "binutils"; }') - promise = _find(root, "promise") - assert can_single_line_promise(promise, 4, 80) is True - - -# --------------------------------------------------------------------------- -# Comment helpers -# --------------------------------------------------------------------------- - - -def test_is_empty_comment_bare_hash(): - node = MockNode("comment", "#") - node.prev_named_sibling = None - node.next_named_sibling = None - assert _is_empty_comment(node) is True - - -def test_is_empty_comment_real_comment(): - node = MockNode("comment", "# real comment") - node.prev_named_sibling = None - node.next_named_sibling = None - assert _is_empty_comment(node) is False - - -def test_is_empty_comment_between_comments(): - a = MockNode("comment", "# above") - b = MockNode("comment", "#") - c = MockNode("comment", "# below") - b.prev_named_sibling = a - b.next_named_sibling = c - a.type = "comment" - c.type = "comment" - assert _is_empty_comment(b) is False - - -def test_skip_comments_forward(): - c1 = MockNode("comment", "# a") - c2 = MockNode("comment", "# b") - target = MockNode("promise", '"x"') - c1.next_named_sibling = c2 - c2.next_named_sibling = target - assert _skip_comments(c1, "next") is target - - -def test_skip_comments_backward(): - target = MockNode("promise", '"x"') - c1 = MockNode("comment", "# a") - c2 = MockNode("comment", "# b") - c2.prev_named_sibling = c1 - c1.prev_named_sibling = target - assert _skip_comments(c2, "prev") is target - - -def test_skip_comments_none(): - assert _skip_comments(None, "next") is None - - -def test_skip_comments_no_non_comment(): - c1 = MockNode("comment", "# a") - c1.next_named_sibling = None - assert _skip_comments(c1, "next") is None - - -def test_comment_indent_next_is_promise(): - target = MockNode("promise", '"x"') - target.next_named_sibling = None - node = MockNode("comment", "# note") - node.next_named_sibling = target - node.prev_named_sibling = None - assert _comment_indent(node, 4) == 6 - - -def test_comment_indent_next_is_not_indented(): - target = MockNode("{", "{") - target.next_named_sibling = None - node = MockNode("comment", "# note") - node.next_named_sibling = target - node.prev_named_sibling = None - assert _comment_indent(node, 4) == 4 - - -def test_comment_indent_no_neighbors(): - node = MockNode("comment", "# note") - node.next_named_sibling = None - node.prev_named_sibling = None - assert _comment_indent(node, 4) == 4 - - -def test_comment_indent_prev_is_attribute(): - target = MockNode("attribute", "x") - target.prev_named_sibling = None - node = MockNode("comment", "# note") - node.next_named_sibling = None - node.prev_named_sibling = target - assert _comment_indent(node, 4) == 6 - - -# --------------------------------------------------------------------------- -# _needs_blank_line_before -# --------------------------------------------------------------------------- - - -def test_needs_blank_line_no_prev(): - node = MockNode("promise", '"x"') - node.prev_named_sibling = None - assert _needs_blank_line_before(node, 4, 80) is False - - -def test_needs_blank_line_bundle_sections(): - prev = MockNode("bundle_section", "vars:") - child = MockNode("bundle_section", "classes:") - child.prev_named_sibling = prev - assert _needs_blank_line_before(child, 0, 80) is True - - -def test_needs_blank_line_class_guard_after_promise(): - prev = MockNode("promise", '"x"') - child = MockNode("class_guarded_promises", "linux::") - child.prev_named_sibling = prev - assert _needs_blank_line_before(child, 4, 80) is True - - -def test_needs_blank_line_unrelated_types(): - prev = MockNode("{", "{") - child = MockNode("}", "}") - child.prev_named_sibling = prev - assert _needs_blank_line_before(child, 0, 80) is False - - # --------------------------------------------------------------------------- # autoformat / format_policy_fin_fout — integration tests # ---------------------------------------------------------------------------