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 and segments in a single pass for capacity hints
28    let mut semicolons = 0usize;
29    let mut commas = 0usize;
30    for &b in bytes {
31        semicolons += (b == b';') as usize;
32        commas += (b == b',') as usize;
33    }
34    let line_count = semicolons + 1;
35    let approx_segments = commas + line_count;
36    let avg_segments_per_line = approx_segments / line_count;
37    let mut mappings: SourceMapMappings = Vec::with_capacity(line_count);
38
39    // Cumulative state across the entire mappings string
40    let mut source_index: i64 = 0;
41    let mut original_line: i64 = 0;
42    let mut original_column: i64 = 0;
43    let mut name_index: i64 = 0;
44
45    let mut pos: usize = 0;
46
47    loop {
48        // Generated column resets per line
49        let mut generated_column: i64 = 0;
50        let mut line: Line = Vec::with_capacity(avg_segments_per_line);
51        let mut saw_semicolon = false;
52
53        while pos < len {
54            let byte = bytes[pos];
55
56            if byte == b';' {
57                pos += 1;
58                saw_semicolon = true;
59                break;
60            }
61
62            if byte == b',' {
63                pos += 1;
64                continue;
65            }
66
67            // Field 1: generated column (always present)
68            let (delta, consumed) = vlq_decode(bytes, pos)?;
69            generated_column += delta;
70            pos += consumed;
71
72            // Build segment with exact allocation (1, 4, or 5 fields)
73            let segment: Segment = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
74                // Field 2: source index
75                let (delta, consumed) = vlq_decode(bytes, pos)?;
76                source_index += delta;
77                pos += consumed;
78
79                // Reject 2-field segments (only 1, 4, or 5 are valid per ECMA-426)
80                if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
81                    return Err(DecodeError::InvalidSegmentLength {
82                        fields: 2,
83                        offset: pos,
84                    });
85                }
86
87                // Field 3: original line
88                let (delta, consumed) = vlq_decode(bytes, pos)?;
89                original_line += delta;
90                pos += consumed;
91
92                // Reject 3-field segments (only 1, 4, or 5 are valid per ECMA-426)
93                if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
94                    return Err(DecodeError::InvalidSegmentLength {
95                        fields: 3,
96                        offset: pos,
97                    });
98                }
99
100                // Field 4: original column
101                let (delta, consumed) = vlq_decode(bytes, pos)?;
102                original_column += delta;
103                pos += consumed;
104
105                // Field 5: name index (optional)
106                if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
107                    let (delta, consumed) = vlq_decode(bytes, pos)?;
108                    name_index += delta;
109                    pos += consumed;
110                    Segment::five(
111                        generated_column,
112                        source_index,
113                        original_line,
114                        original_column,
115                        name_index,
116                    )
117                } else {
118                    Segment::four(
119                        generated_column,
120                        source_index,
121                        original_line,
122                        original_column,
123                    )
124                }
125            } else {
126                Segment::one(generated_column)
127            };
128
129            line.push(segment);
130        }
131
132        mappings.push(line);
133
134        if !saw_semicolon {
135            break;
136        }
137    }
138
139    Ok(mappings)
140}