sourcemap_resolver/
resolver.rs

1use sourcemap::SourceMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4use thiserror::Error;
5
6const LINK_HEADER: &str = "//# sourceMappingURL=";
7
8#[derive(Debug)]
9pub struct ResolveResult {
10    /// Path to source file
11    pub path: PathBuf,
12    /// 1-indexed line number
13    pub line: u32,
14    /// 1-indexed column number
15    pub column: u32,
16}
17
18#[derive(Error, Debug)]
19pub enum ResolveError {
20    #[error("I/O error")]
21    IO(#[from] std::io::Error),
22
23    #[error("SourceMap error")]
24    SourceMap(#[from] sourcemap::Error),
25
26    #[error("Mapping not found")]
27    MappingNotFound,
28
29    #[error("Token not found")]
30    TokenNotFound,
31
32    #[error("Path not found")]
33    PathNotFound,
34}
35
36pub fn resolve<T: AsRef<Path>>(
37    path: T,
38    line: u32,
39    column: Option<u32>,
40) -> Result<ResolveResult, ResolveError> {
41    let src = fs::read_to_string(path.as_ref())?;
42
43    let Some(last_line) = src.lines().last() else {
44        return Err(ResolveError::MappingNotFound);
45    };
46
47    if !last_line.starts_with(LINK_HEADER) {
48        return Err(ResolveError::MappingNotFound);
49    }
50
51    let map_path = last_line.strip_prefix(LINK_HEADER).unwrap();
52    let map_path = path.as_ref().parent().unwrap().join(map_path);
53
54    let map_text = fs::read(&map_path)?;
55    let source_map = SourceMap::from_reader(map_text.as_slice())?;
56
57    // sourcemap crate is based on 0-indexed position
58    let line = line - 1;
59    let column = column.unwrap_or(1) - 1;
60
61    let token = source_map
62        .lookup_token(line, column)
63        .ok_or(ResolveError::TokenNotFound)?;
64
65    let path = token.get_source().ok_or(ResolveError::PathNotFound)?;
66    let path = map_path.parent().unwrap().join(path);
67    let path = fs::canonicalize(path)?;
68    let line = token.get_src_line() + 1;
69    let column = token.get_src_col() + 1;
70
71    Ok(ResolveResult { path, line, column })
72}