Skip to content

Commit 03468ac

Browse files
ENT-14274: Added linting-errors and insertion on format for MISSING-nodes
Ticket: ENT-14274 Changelog: None Signed-off-by: Simon Halvorsen <simon.halvorsen@northern.tech>
1 parent 53770c0 commit 03468ac

3 files changed

Lines changed: 37 additions & 10 deletions

File tree

src/cfengine_cli/format.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def format_json_file(filename: str, check: bool) -> FormatResult:
9898

9999
def text(node: Node) -> str:
100100
"""Extract the UTF-8 text content of a tree-sitter node."""
101+
if node and node.is_missing and not node.is_named:
102+
return node.type
101103
if not node.text:
102104
return ""
103105
return node.text.decode("utf-8")

src/cfengine_cli/lint.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,14 +493,26 @@ def navigate(self, node) -> None:
493493
return
494494

495495

496+
def _last_real_sibling_before(node: Node) -> Node | None:
497+
"""Walk backwards from node through its preceding siblings, skipping
498+
comments and macros, to find the last real (content) sibling."""
499+
sib = node.prev_sibling
500+
while sib is not None and sib.type in ("comment", "macro"):
501+
sib = sib.prev_sibling
502+
return sib
503+
504+
496505
def _check_syntax(policy_file: PolicyFile, state: State) -> int:
497-
"""Iterate over a syntax tree and print errors if it is empty or has syntax
498-
errors.
506+
"""Iterate over a syntax tree and print errors if it is empty, has syntax
507+
errors, or is missing expected tokens (e.g. a dropped ';').
499508
500509
Notably, printing syntax errors _does not happen during parsing_.
501510
502511
Tree sitter has already fully parsed the policy, we iterate over the result
503-
and see if it has inserted any "ERROR" nodes.
512+
and see if it has inserted any "ERROR" nodes, or any "MISSING" nodes
513+
(tree-sitter's error recovery can silently insert a synthetic token,
514+
e.g. a missing ';' right before a closing '}', without ever producing
515+
an ERROR node.
504516
505517
Stops at first error. Returns number of errors, always 0 or 1 in this case.
506518
"""
@@ -522,15 +534,26 @@ def _check_syntax(policy_file: PolicyFile, state: State) -> int:
522534
state.start_file(policy_file)
523535
for node in policy_file.nodes:
524536
state.navigate(node)
525-
if node.type != "ERROR":
526-
continue
527537
line = node.range.start_point[0] + 1
528538
column = node.range.start_point[1] + 1
529-
_highlight_range(node, lines)
530-
location = state.get_location_extended(line, column)
531-
print(f"Error: Syntax error {location}")
532-
state.end_file()
533-
return 1
539+
if node.type == "ERROR":
540+
_highlight_range(node, lines)
541+
location = state.get_location_extended(line, column)
542+
print(f"Error: Syntax error {location}")
543+
state.end_file()
544+
return 1
545+
if node.is_missing:
546+
# To avoid pointing at #comment/@macro -lines
547+
prev = _last_real_sibling_before(node)
548+
report_node = prev if prev is not None else node
549+
_highlight_range(report_node, lines)
550+
line = report_node.range.end_point[0] + 1
551+
column = report_node.range.end_point[1] + 1
552+
missing = node.type if not node.is_named else f"token: {node.type}"
553+
location = state.get_location_extended(line, column)
554+
print(f"Error: Missing '{missing}' {location}")
555+
state.end_file()
556+
return 1
534557
state.end_file()
535558
return 0
536559

tests/unit/test_format.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def __init__(
4242
self.next_named_sibling = next_named_sibling
4343
self.prev_named_sibling = prev_named_sibling
4444
self.parent = parent
45+
self.is_missing = False
46+
self.is_named = True
4547

4648

4749
def _leaf(node_type, node_text=None):

0 commit comments

Comments
 (0)