Skip to main content

srcmap_codec/
decode.rs

1use crate::vlq::vlq_decode;
2use crate::{DecodeError, Line, Segment, SourceMapMappings};
3
4/// Decode a VLQ-encoded source map mappings string into structured data.
5///
6/// The mappings string uses `;` to separate lines and `,` to separate
7/// segments within a line. Each segment is a sequence of VLQ-encoded
8/// values representing column offsets, source indices, and name indices.
9///
10/// Values are relative (delta-encoded) within the mappings string:
11/// - Generated column resets to 0 for each new line
12/// - Source index, original line, original column, and name index
13///   are cumulative across the entire mappings string
14///
15/// # Errors
16///
17/// Returns [`DecodeError`] if the input contains invalid base64 characters,
18/// truncated VLQ sequences, or values that overflow i64.
19pub fn decode(input: &str) -> Result<SourceMapMappings, DecodeError> {
20    if input.is_empty() {
21        return Ok(Vec::new());
22    }
23
24    let bytes = input.as_bytes();
25    let len = bytes.len();
26
27    // Pre-count lines for capacity hint
28    let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
29    let mut mappings: SourceMapMappings = Vec::with_capacity(line_count);
30
31    // Cumulative state across the entire mappings string
32    let mut source_index: i64 = 0;
33    let mut original_line: i64 = 0;
34    let mut original_column: i64 = 0;
35    let mut name_index: i64 = 0;
36
37    let mut pos: usize = 0;
38
39    loop {
40        // Generated column resets per line
41        let mut generated_column: i64 = 0;
42        let mut line: Line = Vec::new();
43        let mut saw_semicolon = false;
44
45        while pos < len {
46            let byte = bytes[pos];
47
48            if byte == b';' {
49                pos += 1;
50                saw_semicolon = true;
51                break;
52            }
53
54            if byte == b',' {
55                pos += 1;
56                continue;
57            }
58
59            // Decode a segment
60            let mut segment: Segment = Vec::with_capacity(5);
61
62            // Field 1: generated column (always present)
63            let (delta, consumed) = vlq_decode(bytes, pos)?;
64            generated_column += delta;
65            segment.push(generated_column);
66            pos += consumed;
67
68            // Check if there are more fields in this segment
69            if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
70                // Field 2: source index
71                let (delta, consumed) = vlq_decode(bytes, pos)?;
72                source_index += delta;
73                segment.push(source_index);
74                pos += consumed;
75
76                // Field 3: original line
77                let (delta, consumed) = vlq_decode(bytes, pos)?;
78                original_line += delta;
79                segment.push(original_line);
80                pos += consumed;
81
82                // Field 4: original column
83                let (delta, consumed) = vlq_decode(bytes, pos)?;
84                original_column += delta;
85                segment.push(original_column);
86                pos += consumed;
87
88                // Field 5: name index (optional)
89                if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
90                    let (delta, consumed) = vlq_decode(bytes, pos)?;
91                    name_index += delta;
92                    segment.push(name_index);
93                    pos += consumed;
94                }
95            }
96
97            debug_assert!(
98                segment.len() == 1 || segment.len() == 4 || segment.len() == 5,
99                "invalid segment length {}",
100                segment.len()
101            );
102
103            line.push(segment);
104        }
105
106        mappings.push(line);
107
108        if !saw_semicolon {
109            break;
110        }
111    }
112
113    Ok(mappings)
114}