tailwind_rs_postcss/advanced_features/
advanced_source_maps.rs1use super::types::*;
7
8pub struct AdvancedSourceMapGenerator {
10 mappings: Vec<SourceMapping>,
11 sources: Vec<SourceFile>,
12 names: Vec<String>,
13 transformations: Vec<Transformation>,
14}
15
16impl AdvancedSourceMapGenerator {
17 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 pub fn generate_source_map(
29 &mut self,
30 css: &str,
31 source_files: &[SourceFile],
32 ) -> Result<SourceMap, AdvancedFeatureError> {
33 self.mappings.clear();
35 self.sources.clear();
36 self.names.clear();
37
38 for source_file in source_files {
40 self.sources.push(source_file.clone());
41 }
42
43 self.generate_css_mappings(css)?;
45
46 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 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 self.generate_transformation_mappings(transformation_clone)?;
70
71 Ok(())
72 }
73
74 pub fn resolve_source_location(
76 &self,
77 line: usize,
78 column: usize,
79 ) -> Result<SourceLocation, AdvancedFeatureError> {
80 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 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 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), name: None,
125 });
126 }
127 }
128
129 Ok(())
130 }
131
132 fn generate_transformation_mappings(
134 &mut self,
135 transformation: Transformation,
136 ) -> Result<(), AdvancedFeatureError> {
137 let source_index = self
139 .sources
140 .iter()
141 .position(|s| s.path == transformation.source_file)
142 .unwrap_or(0);
143
144 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 self.names.push(transformation.name.clone());
157 }
158
159 Ok(())
160 }
161
162 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 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 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 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 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 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 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#[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 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 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 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}