Skip to main content

oak_source_map/
composer.rs

1//! Source Map Composer for combining multiple source maps.
2//!
3//! This is useful when you have a pipeline of transformations,
4//! e.g., TypeScript -> JavaScript -> Minified JavaScript.
5
6use crate::{Mapping, Result, SourceMap, SourceMapBuilder, SourceMapError};
7
8/// Composer for combining multiple source maps.
9///
10/// When you have a chain of transformations, you can compose
11/// the source maps to get a direct mapping from the original
12/// source to the final output.
13#[derive(Debug, Default)]
14pub struct SourceMapComposer {
15    maps: Vec<SourceMap>,
16}
17
18impl SourceMapComposer {
19    /// Creates a new empty composer.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Adds a source map to the composition chain.
25    ///
26    /// Maps should be added in the order of transformations.
27    /// For example, if you have:
28    /// - TypeScript -> JavaScript (map1)
29    /// - JavaScript -> Minified (map2)
30    /// You should add map1 first, then map2.
31    pub fn add(mut self, map: SourceMap) -> Self {
32        self.maps.push(map);
33        self
34    }
35
36    /// Composes all added source maps into one.
37    pub fn compose(self) -> Result<SourceMap> {
38        if self.maps.is_empty() {
39            return Ok(SourceMap::new());
40        }
41
42        if self.maps.len() == 1 {
43            return Ok(self.maps.into_iter().next().unwrap());
44        }
45
46        let mut maps = self.maps.into_iter();
47        let first = maps.next().unwrap();
48
49        let result = maps.try_fold(first, |acc, map| compose_two(&acc, &map))?;
50
51        Ok(result)
52    }
53}
54
55/// Composes two source maps.
56///
57/// The first map goes from generated to intermediate,
58/// the second map goes from intermediate to original.
59/// The result goes from generated to original.
60pub fn compose_two(map1: &SourceMap, map2: &SourceMap) -> Result<SourceMap> {
61    let mut builder = SourceMapBuilder::new();
62
63    let mappings1 = map1.parse_mappings()?;
64
65    for mapping in mappings1 {
66        if let (Some(source_idx), Some(orig_line), Some(orig_col)) = (mapping.source_index, mapping.original_line, mapping.original_column) {
67            let intermediate_source = map1.get_source(source_idx as usize).ok_or_else(|| SourceMapError::InvalidSourceIndex(source_idx as usize))?;
68
69            let intermediate_idx = map2.sources.iter().position(|s| s == intermediate_source);
70
71            if let Some(idx) = intermediate_idx {
72                let decoder = crate::SourceMapDecoder::new(map2.clone())?;
73
74                if let Some(intermediate_mapping) = decoder.lookup(orig_line, orig_col) {
75                    if let (Some(final_source_idx), Some(final_line), Some(final_col)) = (intermediate_mapping.source_index, intermediate_mapping.original_line, intermediate_mapping.original_column) {
76                        let new_source_idx = builder.add_source(map2.get_source(final_source_idx as usize).unwrap_or(""));
77
78                        builder.add_mapping(mapping.generated_line, mapping.generated_column, Some(new_source_idx), Some(final_line), Some(final_col), intermediate_mapping.name_index.or(mapping.name_index));
79                        continue;
80                    }
81                }
82            }
83        }
84
85        builder.add_mapping(mapping.generated_line, mapping.generated_column, None, None, None, None);
86    }
87
88    Ok(builder.build())
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_compose_empty() {
97        let composer = SourceMapComposer::new();
98        let result = composer.compose().unwrap();
99        assert_eq!(result.version, 3);
100    }
101
102    #[test]
103    fn test_compose_single() {
104        let mut sm = SourceMap::new();
105        sm.add_source("test.ts");
106
107        let composer = SourceMapComposer::new().add(sm.clone());
108        let result = composer.compose().unwrap();
109
110        assert_eq!(result.sources, sm.sources);
111    }
112}