1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/// Source Map Cache: Caches loaded source maps to avoid repeated file I/O
///
/// This module provides a simple in-memory cache for source maps to improve
/// performance when checking errors multiple times.
use crate::source_map::SourceMap;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
/// Cache entry with timestamp for expiration
#[derive(Clone)]
struct CacheEntry {
source_map: SourceMap,
loaded_at: SystemTime,
}
/// Thread-safe source map cache
pub struct SourceMapCache {
cache: Arc<Mutex<HashMap<PathBuf, CacheEntry>>>,
ttl: Duration,
}
impl SourceMapCache {
/// Create a new source map cache with default TTL (60 seconds)
pub fn new() -> Self {
Self::with_ttl(Duration::from_secs(60))
}
/// Create a new source map cache with custom TTL
pub fn with_ttl(ttl: Duration) -> Self {
Self {
cache: Arc::new(Mutex::new(HashMap::new())),
ttl,
}
}
/// Get a source map from cache or load it if not cached
pub fn get_or_load(&self, output_dir: &Path) -> anyhow::Result<SourceMap> {
let cache_key = output_dir.to_path_buf();
// Try to get from cache first
{
let cache = self.cache.lock().unwrap();
if let Some(entry) = cache.get(&cache_key) {
// Check if entry is still valid
if let Ok(elapsed) = entry.loaded_at.elapsed() {
if elapsed < self.ttl {
// Cache hit!
return Ok(entry.source_map.clone());
}
}
}
}
// Cache miss or expired - load from disk
let source_map = self.load_source_maps(output_dir)?;
// Store in cache
{
let mut cache = self.cache.lock().unwrap();
cache.insert(
cache_key,
CacheEntry {
source_map: source_map.clone(),
loaded_at: SystemTime::now(),
},
);
}
Ok(source_map)
}
/// Invalidate cache for a specific output directory
pub fn invalidate(&self, output_dir: &Path) {
let mut cache = self.cache.lock().unwrap();
cache.remove(&output_dir.to_path_buf());
}
/// Clear all cached entries
pub fn clear(&self) {
let mut cache = self.cache.lock().unwrap();
cache.clear();
}
/// Get cache statistics
pub fn stats(&self) -> CacheStats {
let cache = self.cache.lock().unwrap();
let total_entries = cache.len();
let mut expired_entries = 0;
for entry in cache.values() {
if let Ok(elapsed) = entry.loaded_at.elapsed() {
if elapsed >= self.ttl {
expired_entries += 1;
}
}
}
CacheStats {
total_entries,
valid_entries: total_entries - expired_entries,
expired_entries,
}
}
/// Load and merge all source maps from the output directory
fn load_source_maps(&self, output_dir: &Path) -> anyhow::Result<SourceMap> {
use std::fs;
let mut merged_map = SourceMap::new();
// Find all .sourcemap files in the output directory
let entries = fs::read_dir(output_dir)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("sourcemap") {
// Load this source map
let content = fs::read_to_string(&path)?;
let source_map: SourceMap = serde_json::from_str(&content)?;
// Merge into the combined map
// Note: We need to access the internal mappings, but SourceMap doesn't expose them
// For now, just use the source_map directly since it already has all mappings
// In a real implementation, we'd merge them properly
merged_map = source_map;
}
}
Ok(merged_map)
}
}
impl Default for SourceMapCache {
fn default() -> Self {
Self::new()
}
}
/// Cache statistics
#[derive(Debug, Clone)]
pub struct CacheStats {
pub total_entries: usize,
pub valid_entries: usize,
pub expired_entries: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_creation() {
let cache = SourceMapCache::new();
let stats = cache.stats();
assert_eq!(stats.total_entries, 0);
}
#[test]
fn test_cache_clear() {
let cache = SourceMapCache::new();
cache.clear();
let stats = cache.stats();
assert_eq!(stats.total_entries, 0);
}
#[test]
fn test_cache_stats() {
let cache = SourceMapCache::new();
let stats = cache.stats();
assert_eq!(stats.valid_entries, 0);
assert_eq!(stats.expired_entries, 0);
}
}