use super::import_rewriter::ImportSourceMap;
use super::SfcBlockType;
use crate::virtual_ts::VizeMapping;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SfcBlockRange {
pub start: u32,
pub end: u32,
pub block_type: SfcBlockType,
}
impl SfcBlockRange {
#[inline]
pub fn contains(self, offset: u32) -> bool {
offset >= self.start && offset < self.end
}
}
#[derive(Debug, Default)]
pub struct SfcSourceMap {
mappings: Vec<VizeMapping>,
blocks: Vec<SfcBlockRange>,
}
impl SfcSourceMap {
pub fn new(mappings: Vec<VizeMapping>, mut blocks: Vec<SfcBlockRange>) -> Self {
blocks.sort_by_key(|block| block.start);
Self { mappings, blocks }
}
pub fn empty() -> Self {
Self::default()
}
pub fn get_original_position(&self, virtual_offset: u32) -> Option<(u32, u32, SfcBlockType)> {
let virtual_offset = virtual_offset as usize;
let mapping = self
.mappings
.iter()
.find(|mapping| mapping.gen_range.contains(&virtual_offset))?;
let delta = virtual_offset.saturating_sub(mapping.gen_range.start);
let src_offset = mapping.src_range.start.saturating_add(delta);
let src_offset = src_offset.min(mapping.src_range.end.saturating_sub(1));
let src_offset = u32::try_from(src_offset).ok()?;
let block = self
.blocks
.iter()
.find(|block| block.contains(src_offset))
.map(|block| block.block_type)
.unwrap_or(SfcBlockType::Script);
Some((src_offset, 0, block))
}
pub fn get_virtual_offset(&self, sfc_offset: u32, block_type: SfcBlockType) -> Option<u32> {
let sfc_offset = usize::try_from(sfc_offset).ok()?;
let block = self
.blocks
.iter()
.find(|block| block.block_type == block_type && block.contains(sfc_offset as u32))?;
if !block.contains(sfc_offset as u32) {
return None;
}
let mapping = self
.mappings
.iter()
.find(|mapping| mapping.src_range.contains(&sfc_offset))?;
let delta = sfc_offset.saturating_sub(mapping.src_range.start);
let virtual_offset = mapping.gen_range.start.saturating_add(delta);
u32::try_from(virtual_offset).ok()
}
pub fn mappings(&self) -> &[VizeMapping] {
&self.mappings
}
}
#[derive(Debug, Default)]
pub struct CompositeSourceMap {
pub sfc_map: Option<SfcSourceMap>,
pub import_map: ImportSourceMap,
}
impl CompositeSourceMap {
pub fn new_vue(sfc_map: SfcSourceMap, import_map: ImportSourceMap) -> Self {
Self {
sfc_map: Some(sfc_map),
import_map,
}
}
pub fn new_script(import_map: ImportSourceMap) -> Self {
Self {
sfc_map: None,
import_map,
}
}
pub fn empty() -> Self {
Self::default()
}
pub fn get_original_position(
&self,
virtual_offset: u32,
) -> Option<(u32, u32, Option<SfcBlockType>)> {
let after_import = self.import_map.get_original_offset(virtual_offset);
if let Some(ref sfc_map) = self.sfc_map {
if let Some((offset, column, block)) = sfc_map.get_original_position(after_import) {
return Some((offset, column, Some(block)));
}
}
Some((after_import, 0, None))
}
}
pub fn offset_to_line_col(content: &str, offset: u32) -> Option<(u32, u32)> {
let offset = offset as usize;
if offset > content.len() {
return None;
}
let mut line = 0u32;
let mut col = 0u32;
let mut current = 0;
for ch in content.chars() {
if current >= offset {
break;
}
if ch == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
current += ch.len_utf8();
}
Some((line, col))
}
pub fn line_col_to_offset(content: &str, line: u32, col: u32) -> Option<u32> {
let mut current_line = 0u32;
let mut current_col = 0u32;
let mut offset = 0u32;
for ch in content.chars() {
if current_line == line && current_col == col {
return Some(offset);
}
if ch == '\n' {
if current_line == line {
return None;
}
current_line += 1;
current_col = 0;
} else {
current_col += 1;
}
offset += ch.len_utf8() as u32;
}
if current_line == line && current_col == col {
return Some(offset);
}
None
}
#[cfg(test)]
mod tests {
use super::{
line_col_to_offset, offset_to_line_col, CompositeSourceMap, ImportSourceMap, SfcBlockRange,
SfcBlockType, SfcSourceMap,
};
use crate::virtual_ts::VizeMapping;
use std::ops::Range;
#[test]
fn test_offset_to_line_col() {
let content = "abc\ndef\nghi";
assert_eq!(offset_to_line_col(content, 0), Some((0, 0)));
assert_eq!(offset_to_line_col(content, 3), Some((0, 3)));
assert_eq!(offset_to_line_col(content, 4), Some((1, 0)));
assert_eq!(offset_to_line_col(content, 8), Some((2, 0)));
}
#[test]
fn test_line_col_to_offset() {
let content = "abc\ndef\nghi";
assert_eq!(line_col_to_offset(content, 0, 0), Some(0));
assert_eq!(line_col_to_offset(content, 0, 3), Some(3));
assert_eq!(line_col_to_offset(content, 1, 0), Some(4));
assert_eq!(line_col_to_offset(content, 2, 0), Some(8));
}
#[test]
fn test_sfc_source_map() {
let map = SfcSourceMap::new(
vec![VizeMapping {
gen_range: Range {
start: 100,
end: 200,
},
src_range: Range {
start: 50,
end: 150,
},
}],
vec![SfcBlockRange {
start: 50,
end: 150,
block_type: SfcBlockType::ScriptSetup,
}],
);
let result = map.get_original_position(150);
assert!(result.is_some());
let (offset, _, block) = result.unwrap();
assert_eq!(offset, 100);
assert_eq!(block, SfcBlockType::ScriptSetup);
}
#[test]
fn test_composite_source_map() {
let sfc_map = SfcSourceMap::empty();
let import_map = ImportSourceMap::empty();
let composite = CompositeSourceMap::new_vue(sfc_map, import_map);
let result = composite.get_original_position(50);
assert!(result.is_some());
}
}