turbopack_core/
debug_id.rs

1use turbo_rcstr::RcStr;
2use turbo_tasks_fs::rope::Rope;
3use twox_hash::xxhash3_128;
4
5/// Generate a deterministic debug ID from content using hash-based UUID generation
6///
7/// This follows the TC39 debug ID proposal by generating UUIDs that are deterministic
8/// based on the content, ensuring reproducible builds while maintaining uniqueness.
9/// Uses xxHash3-128 for fast, stable, and collision-resistant hashing.
10pub fn generate_debug_id(content: &Rope) -> RcStr {
11    let mut hasher = xxhash3_128::Hasher::new();
12    for bytes in content.read() {
13        hasher.write(bytes.as_ref());
14    }
15    let hash = hasher.finish_128();
16    uuid::Uuid::from_u128(hash)
17        .as_hyphenated()
18        .to_string()
19        .into()
20}
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25
26    #[test]
27    fn test_generate_debug_id_deterministic() {
28        // Create test content
29        let content = Rope::from("console.log('Hello World');");
30
31        // Generate debug ID twice
32        let id1 = generate_debug_id(&content);
33        let id2 = generate_debug_id(&content);
34
35        // Should be identical (deterministic)
36        assert_eq!(id1, id2);
37
38        // Should be valid UUID format (8-4-4-4-12)
39        assert_eq!(id1.len(), 36);
40        assert!(id1.contains('-'));
41    }
42
43    #[test]
44    fn test_generate_debug_id_different_content() {
45        // Create two different pieces of content
46        let content1 = Rope::from("console.log('Hello');");
47        let content2 = Rope::from("console.log('World');");
48
49        // Generate debug IDs
50        let id1 = generate_debug_id(&content1);
51        let id2 = generate_debug_id(&content2);
52
53        // Should be different
54        assert_ne!(id1, id2);
55    }
56
57    #[test]
58    fn test_debug_id_format() {
59        let content = Rope::from("test content");
60        let debug_id = generate_debug_id(&content);
61
62        // Verify UUID format: 8-4-4-4-12 characters
63        let parts: Vec<&str> = debug_id.split('-').collect();
64        assert_eq!(parts.len(), 5);
65        assert_eq!(parts[0].len(), 8);
66        assert_eq!(parts[1].len(), 4);
67        assert_eq!(parts[2].len(), 4);
68        assert_eq!(parts[3].len(), 4);
69        assert_eq!(parts[4].len(), 12);
70
71        // Should be lowercase
72        assert_eq!(debug_id, debug_id.to_lowercase());
73    }
74}