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(&mut self, css: &str, source_files: &[SourceFile]) -> Result<SourceMap, AdvancedFeatureError> {
29
30 self.mappings.clear();
32 self.sources.clear();
33 self.names.clear();
34
35 for source_file in source_files {
37 self.sources.push(source_file.clone());
38 }
39
40 self.generate_css_mappings(css)?;
42
43 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 pub fn add_transformation(&mut self, transformation: Transformation) -> Result<(), AdvancedFeatureError> {
59 let transformation_clone = transformation.clone();
60 self.transformations.push(transformation);
61
62 self.generate_transformation_mappings(transformation_clone)?;
64
65 Ok(())
66 }
67
68 pub fn resolve_source_location(&self, line: usize, column: usize) -> Result<SourceLocation, AdvancedFeatureError> {
70 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 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 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), name: None,
115 });
116 }
117 }
118
119 Ok(())
120 }
121
122 fn generate_transformation_mappings(&mut self, transformation: Transformation) -> Result<(), AdvancedFeatureError> {
124 let source_index = self.sources.iter()
126 .position(|s| s.path == transformation.source_file)
127 .unwrap_or(0);
128
129 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 self.names.push(transformation.name.clone());
142 }
143
144 Ok(())
145 }
146
147 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 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 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 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 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 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 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#[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 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 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 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}