Coverage for src/renaissance/integrations/tree_sitter/lst.py: 91%
67 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-02 13:12 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-02 13:12 +0000
1"""AI: Lossless syntax tree (LST) node implementation backed by tree-sitter."""
3import sys
4from typing import Any, Self, cast
6from renaissance.integrations.tree_sitter.kinds import TREE_SITTER_KIND_MAP
7from renaissance.syntax_tree.semantic_kind import SemanticKind
8from renaissance.utils.ast_utils import format_node, match_children, match_props, next_sibling, preceding_sibling
10IRRELEVANT_PROPS = {"source_code", "end_point", "start_point", "location", "type"}
11IRRELEVANT_NODE = {"comment"}
14class LSTNode:
15 """AI: Lossless syntax tree (LST) node implementation backed by tree-sitter."""
17 def __init__(
18 self,
19 node_type: str,
20 properties: dict[str, Any],
21 signature: str,
22 offset: int = 0,
23 children: list[Self] | None = None,
24 parent: Self | None = None,
25 root: Self | None = None,
26 ):
27 """AI: Wrap a tree-sitter node as a language-syntax-tree node with derived semantic kind."""
28 self.root = root or self
29 self.parent = parent
30 self.children = [] if children is None else children
31 self.properties = properties
32 if node_type == "string" and signature.startswith("f"):
33 node_type = "FormattedString"
35 self.parser_kind = node_type
36 self.semantic_kind = TREE_SITTER_KIND_MAP.get(node_type, SemanticKind.NODE)
38 self.is_implicit = True
39 self.show_props = False
40 self.indent = ""
42 self.is_statement = node_type == "Expr"
43 self.referenced_by = []
44 self.references = []
46 self.signature = signature
47 self.text = signature
48 self.filename = "unknown"
49 self.length = len(signature)
50 self.offset = offset
51 self.end_offset = self.offset + self.length
52 self.extended_end_offset = self.end_offset
54 @property
55 def kind_key(self) -> SemanticKind | str:
56 """AI: Return the semantic kind, or the raw parser kind when no semantic kind applies."""
57 return self.semantic_kind if self.semantic_kind is not SemanticKind.NODE else self.parser_kind
59 def __eq__(self, other):
60 """AI: Return whether this node is structurally equal to `other`, ignoring irrelevant properties/children."""
61 return (
62 isinstance(other, type(self))
63 and self.kind_key == other.kind_key
64 and match_props(self.properties, other.properties, IRRELEVANT_PROPS)
65 and match_children(self.children, other.children, IRRELEVANT_NODE)
66 )
68 def __hash__(self):
69 """AI: Return a hash based on the node's kind key, properties, and children."""
70 return hash((self.kind_key, frozenset(self.properties.items()), tuple(self.children)))
72 def match_props(self, properties) -> bool:
73 """AI: Return whether this node's properties match the given properties, ignoring irrelevant ones."""
74 all_keys = (self.properties.keys() | properties.keys()) - IRRELEVANT_PROPS
75 return all(self.properties.get(n) == properties.get(n) for n in all_keys)
77 def match_children(self, children):
78 """AI: Return whether this node's children match the given children at each corresponding index."""
79 return all(i < len(self.children) and self.children[i] == child for i, child in enumerate(children))
81 def add_child(self, child): # LSTNode):
82 """AI: Append child to this node's children and set its parent to this node."""
83 self.children.append(child)
84 child.parent = self
86 @property
87 def preceding_sibling(self) -> Self | None:
88 """AI: Return the sibling node immediately preceding this one, or None."""
89 return preceding_sibling(self)
91 @property
92 def next_sibling(self) -> Self | None:
93 """AI: Return the sibling node immediately following this one, or None."""
94 return next_sibling(self)
96 @property
97 def name(self) -> str:
98 """AI: Return this node's "name" property, or an empty string if absent."""
99 return self.properties.get("name", "")
101 def binary_file_content(self):
102 """AI: Return this node's source code encoded as bytes using the filesystem encoding."""
103 src = cast("str", self.properties.get("source_code"))
104 return src.encode(sys.getfilesystemencoding())
106 @property
107 def node(self):
108 """AI: Return this node itself."""
109 return self
111 def __repr__(self):
112 """AI: Return the formatted node representation."""
113 return format_node(self)
114 # raw_lines = self.signature.splitlines()
115 # properties_text = "" if not self.show_props else self.properties
116 # prefix = " " if len(raw_lines) < 2 else f"\n {self.indent}"
117 # formatted_lines = [f"{prefix}|{line}|" for line in raw_lines]
118 # return (
119 # f"{self.indent}({self.kind}, {self.name},"
120 # f" {self.filename}[{self.offset}:{self.offset + self.length}])"
121 # f"{properties_text}:{''.join(formatted_lines)}\n"
122 # )
124 def is_part_of_translation_unit(self):
125 """AI: Return whether this node belongs to a translation unit (has a root)."""
126 return self.root is not None
129class LST:
130 """AI: Hold the root node of a language-syntax tree."""
132 def __init__(self, root: LSTNode):
133 """AI: Hold the root node of a language-syntax tree."""
134 self.root = root