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