cs/tree/
builder.rs

1//! # Ownership and Borrowing - Rust Book Chapter 4
2//!
3//! This module demonstrates ownership, borrowing, and references from
4//! [The Rust Book Chapter 4](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html).
5//!
6//! ## Key Concepts Demonstrated
7//!
8//! 1. **Borrowing with `&` References** (Chapter 4.2)
9//!    - The `build()` method takes `&SearchResult` instead of `SearchResult`
10//!    - Allows reading data without taking ownership
11//!    - Multiple borrows can exist simultaneously for reading
12//!
13//! 2. **Ownership Transfer with `.clone()`** (Chapter 4.1)
14//!    - `result.query.clone()` creates a new owned `String`
15//!    - Necessary when building nodes that need to own their data
16//!    - Trade-off: memory cost vs. ownership flexibility
17//!
18//! 3. **Iterators and Borrowing** (Chapter 4.2 + 13.2)
19//!    - `.iter()` creates an iterator of references (`&T`)
20//!    - Allows processing collections without taking ownership
21//!    - The original collection remains usable after iteration
22//!
23//! 4. **References in Collections** (Chapter 4.2)
24//!    - `Vec<_>` with `.iter()` creates `Vec<&T>`
25//!    - Temporary collections of references avoid cloning
26//!    - References must not outlive the data they point to
27//!
28//! ## Learning Notes
29//!
30//! **Why use `&SearchResult` instead of `SearchResult`?**
31//! - Caller retains ownership and can reuse the data
32//! - More efficient - no need to clone the entire structure
33//! - Follows Rust convention: borrow when you don't need ownership
34//!
35//! **When to clone vs. when to borrow?**
36//! - Clone: When you need to store data in a new structure (like TreeNode)
37//! - Borrow: When you only need to read data temporarily
38//!
39//! **The `.iter()` pattern:**
40//! ```rust,ignore
41//! for entry in &result.translation_entries {  // Borrows each element
42//!     // entry is &TranslationEntry
43//! }
44//! // vs
45//! for entry in result.translation_entries {   // Takes ownership
46//!     // entry is TranslationEntry (moved)
47//! }
48//! ```
49
50use crate::tree::{Location, NodeType, ReferenceTree, TreeNode};
51use crate::{CodeReference, SearchResult, TranslationEntry};
52
53/// Builder for constructing reference trees from search results.
54///
55/// # Rust Book Reference
56///
57/// **Chapter 4: Understanding Ownership**
58/// https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
59///
60/// This builder demonstrates how to work with borrowed data to construct
61/// owned data structures efficiently.
62pub struct ReferenceTreeBuilder;
63
64impl ReferenceTreeBuilder {
65    /// Build a reference tree from search results.
66    ///
67    /// Creates a hierarchical tree structure:
68    /// - Root: search query text
69    ///   - Translation: translation file entry
70    ///     - KeyPath: full translation key
71    ///       - CodeRef: code reference using the key
72    ///
73    /// # Rust Book Reference
74    ///
75    /// **Chapter 4.2: References and Borrowing**
76    /// https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
77    ///
78    /// # Educational Notes - Borrowing with `&SearchResult`
79    ///
80    /// This method signature demonstrates **immutable borrowing**:
81    ///
82    /// ```rust,ignore
83    /// pub fn build(result: &SearchResult) -> ReferenceTree
84    /// //                   ^
85    /// //                   Borrows result, doesn't take ownership
86    /// ```
87    ///
88    /// **Why borrow instead of taking ownership?**
89    ///
90    /// 1. **Caller keeps ownership:**
91    ///    ```rust,ignore
92    ///    let result = search_translations("add new")?;
93    ///    let tree = ReferenceTreeBuilder::build(&result);  // Borrow
94    ///    // result is still usable here!
95    ///    println!("Found {} entries", result.translation_entries.len());
96    ///    ```
97    ///
98    /// 2. **No unnecessary cloning:**
99    ///    - If we took ownership: `build(result: SearchResult)`
100    ///    - Caller would need: `build(result.clone())` - expensive!
101    ///    - With borrowing: `build(&result)` - zero cost!
102    ///
103    /// 3. **Rust's borrowing rules ensure safety:**
104    ///    - The reference `&result` is valid for the entire function
105    ///    - We can read all fields: `result.query`, `result.translation_entries`
106    ///    - We cannot modify the data (immutable borrow)
107    ///    - The original data cannot be moved while borrowed
108    ///
109    /// **Inside the function:**
110    /// - We borrow fields: `&result.translation_entries`
111    /// - We clone when we need ownership: `result.query.clone()`
112    /// - We iterate with `.iter()` to borrow elements: `for entry in &result.translation_entries`
113    ///
114    /// **Key Insight:** Borrowing is Rust's way of saying "I just need to look at
115    /// this data temporarily, I don't need to own it."
116    pub fn build(result: &SearchResult) -> ReferenceTree {
117        // OWNERSHIP: Clone the query string because TreeNode needs to own its content
118        // Chapter 4.1: The query is a String, which doesn't implement Copy
119        // We must clone to create a new owned value for the TreeNode
120        let mut root = TreeNode::new(NodeType::Root, result.query.clone());
121        let mut used_code_refs = std::collections::HashSet::new();
122
123        // BORROWING: Iterate over references to avoid moving the vector
124        // Chapter 4.2: `&result.translation_entries` borrows the Vec
125        // `for entry in &vec` is shorthand for `for entry in vec.iter()`
126        // Each `entry` is a `&TranslationEntry` (reference)
127        for entry in &result.translation_entries {
128            let mut translation_node = Self::build_translation_node(entry);
129            let mut key_node = Self::build_key_node(entry);
130
131            // ITERATORS AND BORROWING: Build a collection of references
132            // Chapter 13.2: `.iter()` creates an iterator of references
133            // `.enumerate()` adds indices: (usize, &CodeReference)
134            // `.filter()` borrows each element to check the condition
135            // Result: Vec<(usize, &CodeReference)> - no cloning needed!
136            let matching_refs: Vec<_> = result
137                .code_references
138                .iter()
139                .enumerate()
140                .filter(|(_, r)| r.key_path == entry.key)
141                .collect();
142
143            // Add code reference nodes as children of the key node
144            for (idx, code_ref) in matching_refs {
145                let code_node = Self::build_code_node(code_ref);
146                key_node.add_child(code_node);
147                used_code_refs.insert(idx);
148            }
149
150            // Only add the key node if it has code references
151            if key_node.has_children() {
152                translation_node.children.push(key_node);
153            }
154
155            root.add_child(translation_node);
156        }
157
158        // Handle direct matches (code references not associated with any translation entry)
159        let direct_matches: Vec<_> = result
160            .code_references
161            .iter()
162            .enumerate()
163            .filter(|(idx, _)| !used_code_refs.contains(idx))
164            .map(|(_, r)| r)
165            .collect();
166
167        if !direct_matches.is_empty() {
168            // Create a virtual node for direct matches
169            let mut direct_matches_node =
170                TreeNode::new(NodeType::KeyPath, "Direct Matches".to_string());
171
172            for code_ref in direct_matches {
173                let code_node = Self::build_code_node(code_ref);
174                direct_matches_node.add_child(code_node);
175            }
176
177            root.add_child(direct_matches_node);
178        }
179
180        ReferenceTree::new(root)
181    }
182
183    /// Build a translation node from a translation entry.
184    ///
185    /// # Rust Book Reference
186    ///
187    /// **Chapter 4.2: References and Borrowing**
188    /// https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
189    ///
190    /// # Educational Notes - Borrowing vs. Cloning
191    ///
192    /// This method takes `&TranslationEntry` (a reference) but returns an owned `TreeNode`.
193    ///
194    /// **The pattern:**
195    /// ```text
196    /// fn build_translation_node(entry: &TranslationEntry) -> TreeNode
197    /// //                               ^                      ^
198    /// //                               Borrow input          Own output
199    /// ```
200    ///
201    /// **Inside the function:**
202    /// - We borrow: `entry.file`, `entry.line` (read-only access)
203    /// - We clone: `entry.key.clone()`, `entry.value.clone()` (need owned data)
204    /// - We move: `location` into the TreeNode (transfer ownership)
205    ///
206    /// **Why this pattern?**
207    /// - Input: Borrow because we don't need to consume the entry
208    /// - Output: Own because TreeNode needs to live independently
209    /// - Clone: Only the strings we need to store, not the entire entry
210    ///
211    /// **Memory perspective:**
212    /// ```text
213    /// entry (borrowed)          TreeNode (owned)
214    /// ├─ key: "invoice.add"  --clone--> content: "invoice.add"
215    /// ├─ value: "Add New"    --clone--> metadata: "Add New"
216    /// ├─ file: "en.yml"      --clone--> location.file: "en.yml"
217    /// └─ line: 42            --copy---> location.line: 42
218    /// ```
219    fn build_translation_node(entry: &TranslationEntry) -> TreeNode {
220        // CLONE: PathBuf doesn't implement Copy, so we clone to create owned data
221        // Chapter 4.1: Clone creates a deep copy on the heap
222        let location = Location::new(entry.file.clone(), entry.line);
223
224        // CLONE: String doesn't implement Copy, clone creates new heap allocation
225        let mut node = TreeNode::with_location(NodeType::Translation, entry.key.clone(), location);
226
227        // CLONE: Store owned copy of the translation value
228        node.metadata = Some(entry.value.clone());
229
230        // MOVE: Transfer ownership of node to caller
231        // Chapter 4.1: The node is moved out of this function
232        node
233    }
234
235    /// Build a key path node from a translation entry.
236    ///
237    /// # Educational Notes - Minimal Borrowing
238    ///
239    /// This is the simplest borrowing pattern:
240    /// - Borrow the entry: `&TranslationEntry`
241    /// - Clone only what we need: `entry.key.clone()`
242    /// - Return owned result: `TreeNode`
243    ///
244    /// We could have taken ownership: `fn build_key_node(entry: TranslationEntry)`
245    /// But then the caller would lose access to `entry` after calling this function.
246    fn build_key_node(entry: &TranslationEntry) -> TreeNode {
247        TreeNode::new(NodeType::KeyPath, entry.key.clone())
248    }
249
250    /// Build a code reference node.
251    ///
252    /// # Educational Notes - Borrowing Composite Types
253    ///
254    /// `CodeReference` contains multiple fields:
255    /// - `file: PathBuf` - heap-allocated path
256    /// - `line: usize` - stack-allocated number (Copy type)
257    /// - `context: String` - heap-allocated string
258    /// - `key_path: String` - heap-allocated string
259    ///
260    /// By borrowing `&CodeReference`, we can access all fields without cloning
261    /// the entire struct. We only clone the specific fields we need to store.
262    fn build_code_node(code_ref: &CodeReference) -> TreeNode {
263        let location = Location::new(code_ref.file.clone(), code_ref.line);
264        let mut node =
265            TreeNode::with_location(NodeType::CodeRef, code_ref.context.clone(), location);
266        // Store the key path (or search pattern) in metadata for highlighting
267        node.metadata = Some(code_ref.key_path.clone());
268        node
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use std::path::PathBuf;
276
277    fn create_test_translation_entry() -> TranslationEntry {
278        TranslationEntry {
279            key: "invoice.labels.add_new".to_string(),
280            value: "add new".to_string(),
281            line: 4,
282            file: PathBuf::from("en.yml"),
283        }
284    }
285
286    fn create_test_code_reference() -> CodeReference {
287        CodeReference {
288            file: PathBuf::from("invoices.ts"),
289            line: 14,
290            pattern: r#"I18n\.t\(['"]([^'"]+)['"]\)"#.to_string(),
291            context: "I18n.t('invoice.labels.add_new')".to_string(),
292            key_path: "invoice.labels.add_new".to_string(),
293        }
294    }
295
296    #[test]
297    fn test_build_translation_node() {
298        let entry = create_test_translation_entry();
299        let node = ReferenceTreeBuilder::build_translation_node(&entry);
300
301        assert_eq!(node.node_type, NodeType::Translation);
302        assert_eq!(node.content, "invoice.labels.add_new");
303        assert_eq!(node.metadata.as_deref(), Some("add new"));
304        assert!(node.location.is_some());
305        assert_eq!(node.location.unwrap().line, 4);
306    }
307
308    #[test]
309    fn test_build_key_node() {
310        let entry = create_test_translation_entry();
311        let node = ReferenceTreeBuilder::build_key_node(&entry);
312
313        assert_eq!(node.node_type, NodeType::KeyPath);
314        assert_eq!(node.content, "invoice.labels.add_new");
315        assert!(node.location.is_none());
316    }
317
318    #[test]
319    fn test_build_code_node() {
320        let code_ref = create_test_code_reference();
321        let node = ReferenceTreeBuilder::build_code_node(&code_ref);
322
323        assert_eq!(node.node_type, NodeType::CodeRef);
324        assert_eq!(node.content, "I18n.t('invoice.labels.add_new')");
325        assert!(node.location.is_some());
326        assert_eq!(node.location.as_ref().unwrap().line, 14);
327    }
328
329    #[test]
330    fn test_build_tree_single_match() {
331        let result = SearchResult {
332            query: "add new".to_string(),
333            translation_entries: vec![create_test_translation_entry()],
334            code_references: vec![create_test_code_reference()],
335        };
336
337        let tree = ReferenceTreeBuilder::build(&result);
338
339        assert_eq!(tree.root.content, "add new");
340        assert_eq!(tree.root.node_type, NodeType::Root);
341        assert_eq!(tree.root.children.len(), 1);
342
343        // Check translation node
344        let translation = &tree.root.children[0];
345        assert_eq!(translation.node_type, NodeType::Translation);
346        assert_eq!(translation.content, "invoice.labels.add_new");
347        assert_eq!(translation.metadata.as_deref(), Some("add new"));
348        assert_eq!(translation.children.len(), 1);
349
350        // Check key path node
351        let key_path = &translation.children[0];
352        assert_eq!(key_path.node_type, NodeType::KeyPath);
353        assert_eq!(key_path.content, "invoice.labels.add_new");
354        assert_eq!(key_path.children.len(), 1);
355
356        // Check code reference node
357        let code_ref = &key_path.children[0];
358        assert_eq!(code_ref.node_type, NodeType::CodeRef);
359        assert!(code_ref.content.contains("I18n.t"));
360    }
361
362    #[test]
363    fn test_build_tree_multiple_code_refs() {
364        let entry = create_test_translation_entry();
365        let code_ref1 = create_test_code_reference();
366        let mut code_ref2 = create_test_code_reference();
367        code_ref2.line = 20;
368        code_ref2.context = "I18n.t('invoice.labels.add_new') // another usage".to_string();
369
370        let result = SearchResult {
371            query: "add new".to_string(),
372            translation_entries: vec![entry],
373            code_references: vec![code_ref1, code_ref2],
374        };
375
376        let tree = ReferenceTreeBuilder::build(&result);
377
378        // Should have one translation node
379        assert_eq!(tree.root.children.len(), 1);
380
381        // Translation should have one key path node
382        let translation = &tree.root.children[0];
383        assert_eq!(translation.children.len(), 1);
384
385        // Key path should have two code reference nodes
386        let key_path = &translation.children[0];
387        assert_eq!(key_path.children.len(), 2);
388    }
389
390    #[test]
391    fn test_build_tree_multiple_translations() {
392        let entry1 = create_test_translation_entry();
393        let mut entry2 = create_test_translation_entry();
394        entry2.key = "invoice.labels.edit".to_string();
395        entry2.value = "edit invoice".to_string();
396
397        let code_ref1 = create_test_code_reference();
398        let mut code_ref2 = create_test_code_reference();
399        code_ref2.key_path = "invoice.labels.edit".to_string();
400        code_ref2.context = "I18n.t('invoice.labels.edit')".to_string();
401
402        let result = SearchResult {
403            query: "invoice".to_string(),
404            translation_entries: vec![entry1, entry2],
405            code_references: vec![code_ref1, code_ref2],
406        };
407
408        let tree = ReferenceTreeBuilder::build(&result);
409
410        // Should have two translation nodes
411        assert_eq!(tree.root.children.len(), 2);
412
413        // Each translation should have one key path with one code ref
414        for translation in &tree.root.children {
415            assert_eq!(translation.children.len(), 1);
416            assert_eq!(translation.children[0].children.len(), 1);
417        }
418    }
419
420    #[test]
421    fn test_build_tree_no_code_refs() {
422        let result = SearchResult {
423            query: "add new".to_string(),
424            translation_entries: vec![create_test_translation_entry()],
425            code_references: vec![],
426        };
427
428        let tree = ReferenceTreeBuilder::build(&result);
429
430        // Should have one translation node
431        assert_eq!(tree.root.children.len(), 1);
432
433        // Translation should have no children (no key path without code refs)
434        let translation = &tree.root.children[0];
435        assert_eq!(translation.children.len(), 0);
436    }
437
438    #[test]
439    fn test_build_tree_empty_result() {
440        let result = SearchResult {
441            query: "nonexistent".to_string(),
442            translation_entries: vec![],
443            code_references: vec![],
444        };
445
446        let tree = ReferenceTreeBuilder::build(&result);
447
448        assert_eq!(tree.root.content, "nonexistent");
449        assert_eq!(tree.root.children.len(), 0);
450        assert!(!tree.has_results());
451    }
452
453    #[test]
454    fn test_build_tree_structure() {
455        let result = SearchResult {
456            query: "add new".to_string(),
457            translation_entries: vec![create_test_translation_entry()],
458            code_references: vec![create_test_code_reference()],
459        };
460
461        let tree = ReferenceTreeBuilder::build(&result);
462
463        // Verify tree structure
464        assert_eq!(tree.node_count(), 4); // root + translation + key + code_ref
465        assert_eq!(tree.max_depth(), 4);
466        assert!(tree.has_results());
467    }
468
469    #[test]
470    fn test_build_tree_filters_unmatched_code_refs() {
471        let entry = create_test_translation_entry();
472        let code_ref1 = create_test_code_reference();
473        let mut code_ref2 = create_test_code_reference();
474        code_ref2.key_path = "different.key".to_string();
475
476        let result = SearchResult {
477            query: "add new".to_string(),
478            translation_entries: vec![entry],
479            code_references: vec![code_ref1, code_ref2],
480        };
481
482        let tree = ReferenceTreeBuilder::build(&result);
483
484        // Should only include the matching code ref
485        let key_path = &tree.root.children[0].children[0];
486        assert_eq!(key_path.children.len(), 1);
487        assert!(key_path.children[0]
488            .content
489            .contains("invoice.labels.add_new"));
490    }
491}