cs/tree/
node.rs

1use std::path::PathBuf;
2
3/// Type of node in the reference tree
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum NodeType {
6    /// Root node containing the search text
7    Root,
8    /// Translation file entry
9    Translation,
10    /// Full key path (e.g., "invoice.labels.add_new")
11    KeyPath,
12    /// Code reference where the key is used
13    CodeRef,
14}
15
16/// Location information for a node
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Location {
19    pub file: PathBuf,
20    pub line: usize,
21}
22
23impl Location {
24    pub fn new(file: PathBuf, line: usize) -> Self {
25        Self { file, line }
26    }
27}
28
29/// A node in the reference tree
30#[derive(Debug, Clone)]
31pub struct TreeNode {
32    pub node_type: NodeType,
33    pub content: String,
34    pub location: Option<Location>,
35    pub children: Vec<TreeNode>,
36    pub metadata: Option<String>,
37}
38
39impl TreeNode {
40    /// Create a new TreeNode
41    pub fn new(node_type: NodeType, content: String) -> Self {
42        Self {
43            node_type,
44            content,
45            location: None,
46            children: Vec::new(),
47            metadata: None,
48        }
49    }
50
51    /// Create a TreeNode with a location
52    pub fn with_location(node_type: NodeType, content: String, location: Location) -> Self {
53        Self {
54            node_type,
55            content,
56            location: Some(location),
57            children: Vec::new(),
58            metadata: None,
59        }
60    }
61
62    /// Add a child node
63    pub fn add_child(&mut self, child: TreeNode) {
64        self.children.push(child);
65    }
66
67    /// Check if this node has children
68    pub fn has_children(&self) -> bool {
69        !self.children.is_empty()
70    }
71
72    /// Get the number of children
73    pub fn child_count(&self) -> usize {
74        self.children.len()
75    }
76
77    /// Get the total number of nodes in the tree (including this node)
78    pub fn node_count(&self) -> usize {
79        1 + self.children.iter().map(|c| c.node_count()).sum::<usize>()
80    }
81
82    /// Get the maximum depth of the tree
83    pub fn max_depth(&self) -> usize {
84        if self.children.is_empty() {
85            1
86        } else {
87            1 + self
88                .children
89                .iter()
90                .map(|c| c.max_depth())
91                .max()
92                .unwrap_or(0)
93        }
94    }
95}
96
97/// A reference tree representing the search results
98#[derive(Debug)]
99pub struct ReferenceTree {
100    pub root: TreeNode,
101}
102
103impl ReferenceTree {
104    /// Create a new ReferenceTree with a root node
105    pub fn new(root: TreeNode) -> Self {
106        Self { root }
107    }
108
109    /// Create a ReferenceTree with a root containing the search text
110    pub fn with_search_text(search_text: String) -> Self {
111        Self {
112            root: TreeNode::new(NodeType::Root, search_text),
113        }
114    }
115
116    /// Get the total number of nodes in the tree
117    pub fn node_count(&self) -> usize {
118        self.root.node_count()
119    }
120
121    /// Get the maximum depth of the tree
122    pub fn max_depth(&self) -> usize {
123        self.root.max_depth()
124    }
125
126    /// Check if the tree has any results (children of root)
127    pub fn has_results(&self) -> bool {
128        self.root.has_children()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_create_tree_node() {
138        let node = TreeNode::new(NodeType::Root, "add new".to_string());
139        assert_eq!(node.content, "add new");
140        assert_eq!(node.node_type, NodeType::Root);
141        assert!(node.location.is_none());
142        assert!(node.children.is_empty());
143    }
144
145    #[test]
146    fn test_create_tree_node_with_location() {
147        let location = Location::new(PathBuf::from("test.yml"), 10);
148        let node = TreeNode::with_location(
149            NodeType::Translation,
150            "add_new: 'add new'".to_string(),
151            location.clone(),
152        );
153
154        assert_eq!(node.content, "add_new: 'add new'");
155        assert_eq!(node.node_type, NodeType::Translation);
156        assert!(node.location.is_some());
157        assert_eq!(node.location.unwrap().line, 10);
158    }
159
160    #[test]
161    fn test_add_child() {
162        let mut parent = TreeNode::new(NodeType::Root, "root".to_string());
163        let child = TreeNode::new(NodeType::Translation, "child".to_string());
164
165        assert_eq!(parent.child_count(), 0);
166        parent.add_child(child);
167        assert_eq!(parent.child_count(), 1);
168        assert!(parent.has_children());
169    }
170
171    #[test]
172    fn test_node_count() {
173        let mut root = TreeNode::new(NodeType::Root, "root".to_string());
174        let mut child1 = TreeNode::new(NodeType::Translation, "child1".to_string());
175        let child2 = TreeNode::new(NodeType::Translation, "child2".to_string());
176        let grandchild = TreeNode::new(NodeType::KeyPath, "grandchild".to_string());
177
178        child1.add_child(grandchild);
179        root.add_child(child1);
180        root.add_child(child2);
181
182        // root + child1 + child2 + grandchild = 4
183        assert_eq!(root.node_count(), 4);
184    }
185
186    #[test]
187    fn test_max_depth() {
188        let mut root = TreeNode::new(NodeType::Root, "root".to_string());
189        let mut child = TreeNode::new(NodeType::Translation, "child".to_string());
190        let grandchild = TreeNode::new(NodeType::KeyPath, "grandchild".to_string());
191
192        // Depth 1: just root
193        assert_eq!(root.max_depth(), 1);
194
195        // Depth 2: root -> child
196        root.add_child(child.clone());
197        assert_eq!(root.max_depth(), 2);
198
199        // Depth 3: root -> child -> grandchild
200        child.add_child(grandchild);
201        root.children[0] = child;
202        assert_eq!(root.max_depth(), 3);
203    }
204
205    #[test]
206    fn test_reference_tree_creation() {
207        let tree = ReferenceTree::with_search_text("add new".to_string());
208        assert_eq!(tree.root.content, "add new");
209        assert_eq!(tree.root.node_type, NodeType::Root);
210        assert!(!tree.has_results());
211    }
212
213    #[test]
214    fn test_reference_tree_with_results() {
215        let mut root = TreeNode::new(NodeType::Root, "add new".to_string());
216        let child = TreeNode::new(NodeType::Translation, "translation".to_string());
217        root.add_child(child);
218
219        let tree = ReferenceTree::new(root);
220        assert!(tree.has_results());
221        assert_eq!(tree.node_count(), 2);
222        assert_eq!(tree.max_depth(), 2);
223    }
224
225    #[test]
226    fn test_location_creation() {
227        let location = Location::new(PathBuf::from("test.yml"), 42);
228        assert_eq!(location.file, PathBuf::from("test.yml"));
229        assert_eq!(location.line, 42);
230    }
231
232    #[test]
233    fn test_node_types() {
234        let root = NodeType::Root;
235        let translation = NodeType::Translation;
236        let key_path = NodeType::KeyPath;
237        let code_ref = NodeType::CodeRef;
238
239        assert_eq!(root, NodeType::Root);
240        assert_eq!(translation, NodeType::Translation);
241        assert_eq!(key_path, NodeType::KeyPath);
242        assert_eq!(code_ref, NodeType::CodeRef);
243    }
244
245    #[test]
246    fn test_complex_tree_structure() {
247        // Build a tree: root -> translation -> key_path -> code_ref
248        let mut root = TreeNode::new(NodeType::Root, "add new".to_string());
249
250        let mut translation = TreeNode::with_location(
251            NodeType::Translation,
252            "add_new: 'add new'".to_string(),
253            Location::new(PathBuf::from("en.yml"), 4),
254        );
255
256        let mut key_path = TreeNode::new(NodeType::KeyPath, "invoice.labels.add_new".to_string());
257
258        let code_ref = TreeNode::with_location(
259            NodeType::CodeRef,
260            "I18n.t('invoice.labels.add_new')".to_string(),
261            Location::new(PathBuf::from("invoices.ts"), 14),
262        );
263
264        key_path.add_child(code_ref);
265        translation.add_child(key_path);
266        root.add_child(translation);
267
268        let tree = ReferenceTree::new(root);
269
270        assert_eq!(tree.node_count(), 4);
271        assert_eq!(tree.max_depth(), 4);
272        assert!(tree.has_results());
273    }
274}