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