vlq-parser 0.9.0

VLQ encoded sourcemap decoder
Documentation
pub type RawMappings = Vec<Vec<Vec<i32>>>;
pub type Mappings = Vec<Vec<Option<(i32, i32, i32, i32)>>>;
use std::collections::HashMap;

pub const COMMA_CHAR: char = ',';
pub const SPACE_CHAR: char = ' ';
pub const SEMICOLON_CHAR: char = ';';
pub const VLQ_TABLE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

fn create_lookup_tables() -> (HashMap<char, u8>, Vec<char>) {
    let chars = VLQ_TABLE;
    let mut char_to_integer = HashMap::new();
    let mut integer_to_char = vec![SPACE_CHAR; 65];

    for (i, c) in chars.chars().enumerate() {
        char_to_integer.insert(c, i as u8);
        integer_to_char[i] = c;
    }

    (char_to_integer, integer_to_char)
}

fn decode(string: &str) -> Vec<i32> {
    let (char_to_integer, _) = create_lookup_tables();
    let mut result = Vec::new();
    let mut shift = 0;
    let mut value = 0;

    for c in string.chars() {
        let integer = match char_to_integer.get(&c) {
            Some(&val) => val as i32,
            None => continue,
        };

        let has_continuation_bit = integer & 32;
        let integer = integer & 31;
        value += integer << shift;

        if has_continuation_bit != 0 {
            shift += 5;
        } else {
            let should_negate = value & 1;
            value >>= 1;

            if should_negate != 0 {
                result.push(if value == 0 { -0x80000000 } else { -value });
            } else {
                result.push(value);
            }
            value = 0;
            shift = 0;
        }
    }
    result
}

pub fn parse(mappings: &str) -> Mappings {
    let vlqs = mappings
        .split(SEMICOLON_CHAR)
        .map(|line| line.split(COMMA_CHAR).map(decode).collect::<Vec<_>>())
        .collect::<Vec<_>>();

    process(vlqs)
}

fn process(decoded: RawMappings) -> Mappings {
    let mut source_file_index = 0;
    let mut source_code_line = 0;
    let mut source_code_column = 0;

    decoded
        .into_iter()
        .map(|line| {
            let mut generated_code_column = 0;

            line.into_iter()
                .map(|segment| {
                    if segment.len() == 0 {
                        return None;
                    }
                    generated_code_column += segment[0];

                    source_file_index += segment[1];
                    source_code_line += segment[2];
                    source_code_column += segment[3];

                    Some((
                        generated_code_column,
                        source_file_index,
                        source_code_line,
                        source_code_column,
                    ))
                })
                .collect()
        })
        .collect()
}

pub fn to_source(mappings: &Mappings, line: usize, column: usize) -> Option<(usize, usize)> {
    let Some(line_mappings) = mappings.get(line - 1) else {
        return None;
    };

    for &segment in line_mappings {
        let Some((gen_col, _, src_line, src_col)) = segment else {
            continue;
        };
        if gen_col as usize >= column {
            return Some((((src_line + 1) as usize), src_col as usize));
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use crate::{parse, to_source};

    #[test]
    fn test_dummy() {
        let input = ";;;;IAQmB,OAAO,GAAE,MAAM,CAAA;;OARzB,KAAK,MAAA,aAAA,CAAA;OACP,CAAQ,MAAA,8BAAA,CAAA;AAEf,MAAM,MAAM,GAAG,MAAM,CAAC;OAIV,SAAA,MAAA;IAFZ,YAAA,MAAA,EAAA,MAAA,EAAA,cAAA,EAAA,MAAA,GAAA,CAAA,CAAA,EAAA,YAAA,GAAA,SAAA,EAAA,SAAA;;;;;uBAGqC,aAAa,CAAA;;;IAL5B,CAAA;;;;;;;;;;;;;;IAKpB,OAAO,CAAC,QAAQ,CAAU,OAAA,EAAA,MAAM,CAAiB;IAEjD,aAAA;;YACE,GAAG,CAAA,MAAA,EAAA,CAAA;YAAH,GAAG,CAuBF,MAAM,CAAC,MAAM,CAAA,CAAA;;;YAtBZ,MAAM,CAAA,MAAA,EAAA,CAAA;YAAN,MAAM,CAoBL,KAAK,CAAC,MAAM,CAAA,CAAA;;;YAnBX,IAAI,QAAC,UAAU,CAAA,CAAA;YAAf,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ,GAAO,CAAA;YACT,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;;YAMJ,IAAI,QAAC,WAAW,CAAA,CAAA;YAAhB,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,8BAA8B,EAAE,EAAS,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACpF,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;;YAMJ,IAAI,QAAC,WAAW,CAAA,CAAA;YAAhB,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ;oBAAO,CAAC;YACV,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;QAbN,MAAM,CAAA,GAAA,EAAA,CAAA;QADR,GAAG,CAAA,GAAA,EAAA,CAAA;IAwBJ,CAAA;;;;;;;;AAGH;IACE,IAAO,CAAA;AACT,CAAC;AAED;IACE,IAAO,CAAA;AACT,CAAC;AACD;IACE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;";
        let mappings = parse(input);
        let target = to_source(&mappings, 54, 1);
        assert!(target == Some((20, 8)));

        let target = to_source(&mappings, 52, 1);
        println!("{:?}", target);
        assert!(target == None);

        let target = to_source(&mappings, 40, 1);
        println!("{:?}", target);
        assert!(target == Some((13, 6)));
    }
}