sourcemap_resolver/
resolver.rs1use 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 pub path: PathBuf,
12 pub line: u32,
14 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 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}