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