tailwind_rs_postcss/advanced_features/
advanced_source_maps.rs

1//! Advanced Source Maps
2//! 
3//! This module provides comprehensive source map generation with
4//! detailed mappings, transformation tracking, and source resolution.
5
6use super::types::*;
7
8/// Advanced source map generator
9pub struct AdvancedSourceMapGenerator {
10    mappings: Vec<SourceMapping>,
11    sources: Vec<SourceFile>,
12    names: Vec<String>,
13    transformations: Vec<Transformation>,
14}
15
16impl AdvancedSourceMapGenerator {
17    /// Create new advanced source map generator
18    pub fn new() -> Self {
19        Self {
20            mappings: Vec::new(),
21            sources: Vec::new(),
22            names: Vec::new(),
23            transformations: Vec::new(),
24        }
25    }
26    
27    /// Generate comprehensive source map
28    pub fn generate_source_map(&mut self, css: &str, source_files: &[SourceFile]) -> Result<SourceMap, AdvancedFeatureError> {
29        
30        // Clear previous mappings
31        self.mappings.clear();
32        self.sources.clear();
33        self.names.clear();
34        
35        // Add source files
36        for source_file in source_files {
37            self.sources.push(source_file.clone());
38        }
39        
40        // Generate mappings for CSS
41        self.generate_css_mappings(css)?;
42        
43        // Generate source map
44        let source_map = SourceMap {
45            version: 3,
46            file: "output.css".to_string(),
47            source_root: String::new(),
48            sources: self.sources.iter().map(|s| s.path.clone()).collect(),
49            names: self.names.clone(),
50            mappings: self.encode_mappings(&self.mappings)?,
51            sources_content: self.sources.iter().map(|s| s.content.clone()).collect(),
52        };
53        
54        Ok(source_map)
55    }
56    
57    /// Add transformation mapping
58    pub fn add_transformation(&mut self, transformation: Transformation) -> Result<(), AdvancedFeatureError> {
59        let transformation_clone = transformation.clone();
60        self.transformations.push(transformation);
61        
62        // Generate mappings for transformation
63        self.generate_transformation_mappings(transformation_clone)?;
64        
65        Ok(())
66    }
67    
68    /// Resolve source location
69    pub fn resolve_source_location(&self, line: usize, column: usize) -> Result<SourceLocation, AdvancedFeatureError> {
70        // Find mapping for position
71        for mapping in &self.mappings {
72            if mapping.generated_line == line && mapping.generated_column == column {
73                let source_file = if let Some(source_index) = mapping.source_file {
74                    if source_index < self.sources.len() {
75                        &self.sources[source_index]
76                    } else {
77                        return Err(AdvancedFeatureError::SourceMapFailed {
78                            error: "Invalid source file index".to_string()
79                        });
80                    }
81                } else {
82                    return Err(AdvancedFeatureError::SourceMapFailed {
83                        error: "No source file for mapping".to_string()
84                    });
85                };
86                
87                return Ok(SourceLocation {
88                    file: source_file.path.clone(),
89                    line: mapping.source_line,
90                    column: mapping.source_column,
91                    content: self.get_source_content(source_file, mapping.source_line)?,
92                });
93            }
94        }
95        
96        Err(AdvancedFeatureError::SourceMapFailed {
97            error: "No mapping found for position".to_string()
98        })
99    }
100    
101    /// Generate CSS mappings
102    fn generate_css_mappings(&mut self, css: &str) -> Result<(), AdvancedFeatureError> {
103        let lines: Vec<&str> = css.split('\n').collect();
104        
105        for (line_num, line) in lines.iter().enumerate() {
106            for (col_num, _) in line.char_indices() {
107                // Create 1:1 mapping for each character
108                self.mappings.push(SourceMapping {
109                    generated_line: line_num + 1,
110                    generated_column: col_num,
111                    source_line: line_num + 1,
112                    source_column: col_num,
113                    source_file: Some(0), // First source file
114                    name: None,
115                });
116            }
117        }
118        
119        Ok(())
120    }
121    
122    /// Generate transformation mappings
123    fn generate_transformation_mappings(&mut self, transformation: Transformation) -> Result<(), AdvancedFeatureError> {
124        // Find source file index
125        let source_index = self.sources.iter()
126            .position(|s| s.path == transformation.source_file)
127            .unwrap_or(0);
128        
129        // Generate mappings for transformation
130        for line in transformation.input_range.start..transformation.output_range.end {
131            self.mappings.push(SourceMapping {
132                generated_line: line,
133                generated_column: 0,
134                source_line: line,
135                source_column: 0,
136                source_file: Some(source_index),
137                name: Some(self.names.len()),
138            });
139            
140            // Add transformation name
141            self.names.push(transformation.name.clone());
142        }
143        
144        Ok(())
145    }
146    
147    /// Encode mappings using VLQ
148    fn encode_mappings(&self, mappings: &[SourceMapping]) -> Result<String, AdvancedFeatureError> {
149        let mut encoded = String::new();
150        let mut previous_generated_line = 0;
151        let mut previous_generated_column = 0;
152        let mut previous_source_line = 0;
153        let mut previous_source_column = 0;
154        let mut previous_source_file = 0;
155        let mut previous_name = 0;
156        
157        for mapping in mappings {
158            // Calculate deltas
159            let generated_line_delta = (mapping.generated_line - previous_generated_line) as isize;
160            let generated_column_delta = (mapping.generated_column - previous_generated_column) as isize;
161            let source_line_delta = (mapping.source_line - previous_source_line) as isize;
162            let source_column_delta = (mapping.source_column - previous_source_column) as isize;
163            let source_file_delta = (mapping.source_file.unwrap_or(0) - previous_source_file) as isize;
164            let name_delta = (mapping.name.unwrap_or(0) - previous_name) as isize;
165            
166            // Encode deltas
167            if !encoded.is_empty() {
168                encoded.push(';');
169            }
170            
171            encoded.push_str(&self.encode_vlq(generated_line_delta));
172            encoded.push(',');
173            encoded.push_str(&self.encode_vlq(generated_column_delta));
174            encoded.push(',');
175            encoded.push_str(&self.encode_vlq(source_file_delta));
176            encoded.push(',');
177            encoded.push_str(&self.encode_vlq(source_line_delta));
178            encoded.push(',');
179            encoded.push_str(&self.encode_vlq(source_column_delta));
180            
181            if mapping.name.is_some() {
182                encoded.push(',');
183                encoded.push_str(&self.encode_vlq(name_delta));
184            }
185            
186            // Update previous values
187            previous_generated_line = mapping.generated_line;
188            previous_generated_column = mapping.generated_column;
189            previous_source_line = mapping.source_line;
190            previous_source_column = mapping.source_column;
191            previous_source_file = mapping.source_file.unwrap_or(0);
192            previous_name = mapping.name.unwrap_or(0);
193        }
194        
195        Ok(encoded)
196    }
197    
198    /// Encode number using VLQ
199    fn encode_vlq(&self, mut num: isize) -> String {
200        let mut result = String::new();
201        
202        if num < 0 {
203            num = (-num << 1) | 1;
204        } else {
205            num <<= 1;
206        }
207        
208        loop {
209            let mut digit = (num & 0x1f) as u8;
210            num >>= 5;
211            
212            if num > 0 {
213                digit |= 0x20;
214            }
215            
216            result.push(self.vlq_to_char(digit));
217            
218            if num == 0 {
219                break;
220            }
221        }
222        
223        result
224    }
225    
226    /// Convert VLQ digit to character
227    fn vlq_to_char(&self, digit: u8) -> char {
228        const VLQ_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
229        VLQ_CHARS.chars().nth(digit as usize).unwrap_or('A')
230    }
231    
232    /// Get source content for line
233    fn get_source_content(&self, source_file: &SourceFile, line: usize) -> Result<String, AdvancedFeatureError> {
234        let lines: Vec<&str> = source_file.content.split('\n').collect();
235        
236        if line > 0 && line <= lines.len() {
237            Ok(lines[line - 1].to_string())
238        } else {
239            Ok(String::new())
240        }
241    }
242}
243
244/// Source map structure
245#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
246pub struct SourceMap {
247    pub version: u32,
248    pub file: String,
249    pub source_root: String,
250    pub sources: Vec<String>,
251    pub names: Vec<String>,
252    pub mappings: String,
253    pub sources_content: Vec<String>,
254}
255
256impl SourceMap {
257    /// Create new source map
258    pub fn new() -> Self {
259        Self {
260            version: 3,
261            file: String::new(),
262            source_root: String::new(),
263            sources: Vec::new(),
264            names: Vec::new(),
265            mappings: String::new(),
266            sources_content: Vec::new(),
267        }
268    }
269    
270    /// Get source map as JSON
271    pub fn to_json(&self) -> Result<String, AdvancedFeatureError> {
272        serde_json::to_string_pretty(self)
273            .map_err(|e| AdvancedFeatureError::SourceMapFailed {
274                error: format!("Failed to serialize source map: {}", e)
275            })
276    }
277    
278    /// Load source map from JSON
279    pub fn from_json(json: &str) -> Result<Self, AdvancedFeatureError> {
280        serde_json::from_str(json)
281            .map_err(|e| AdvancedFeatureError::SourceMapFailed {
282                error: format!("Failed to parse source map: {}", e)
283            })
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    
291    #[test]
292    fn test_source_map_generation() {
293        let mut generator = AdvancedSourceMapGenerator::new();
294        let css = ".test { color: red; }";
295        let source_files = vec![
296            SourceFile {
297                path: "input.css".to_string(),
298                content: css.to_string(),
299                mtime: std::time::SystemTime::now(),
300                size: css.len(),
301            }
302        ];
303        
304        let result = generator.generate_source_map(css, &source_files);
305        assert!(result.is_ok());
306        
307        let source_map = result.unwrap();
308        assert_eq!(source_map.version, 3);
309        assert!(!source_map.sources.is_empty());
310        assert!(!source_map.mappings.is_empty());
311    }
312    
313    #[test]
314    fn test_transformation_mapping() {
315        let mut generator = AdvancedSourceMapGenerator::new();
316        let transformation = Transformation {
317            name: "autoprefixer".to_string(),
318            input_range: TextRange::new(0, 10),
319            output_range: TextRange::new(0, 15),
320            source_file: "input.css".to_string(),
321        };
322        
323        let result = generator.add_transformation(transformation);
324        assert!(result.is_ok());
325    }
326    
327    #[test]
328    fn test_source_location_resolution() {
329        let mut generator = AdvancedSourceMapGenerator::new();
330        let css = ".test { color: red; }";
331        let source_files = vec![
332            SourceFile {
333                path: "input.css".to_string(),
334                content: css.to_string(),
335                mtime: std::time::SystemTime::now(),
336                size: css.len(),
337            }
338        ];
339        
340        generator.generate_source_map(css, &source_files).unwrap();
341        
342        let result = generator.resolve_source_location(1, 0);
343        assert!(result.is_ok());
344        
345        let location = result.unwrap();
346        assert_eq!(location.file, "input.css");
347        assert_eq!(location.line, 1);
348    }
349    
350    #[test]
351    fn test_vlq_encoding() {
352        let generator = AdvancedSourceMapGenerator::new();
353        let encoded = generator.encode_vlq(123);
354        assert!(!encoded.is_empty());
355    }
356    
357    #[test]
358    fn test_source_map_json() {
359        let source_map = SourceMap::new();
360        let json = source_map.to_json();
361        assert!(json.is_ok());
362        
363        let json_str = json.unwrap();
364        let parsed = SourceMap::from_json(&json_str);
365        assert!(parsed.is_ok());
366    }
367}