use serde::{Deserialize, Serialize};
use crate::CompactString as String;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SourceRange {
pub start: u32,
pub end: u32,
}
impl SourceRange {
#[inline]
pub const fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
#[inline]
pub const fn point(offset: u32) -> Self {
Self {
start: offset,
end: offset,
}
}
#[inline]
pub const fn contains(&self, offset: u32) -> bool {
offset >= self.start && offset < self.end
}
#[inline]
pub const fn len(&self) -> u32 {
self.end.saturating_sub(self.start)
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.start >= self.end
}
#[inline]
pub const fn intersects(&self, other: &Self) -> bool {
self.start < other.end && other.start < self.end
}
#[inline]
pub fn union(&self, other: &Self) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
#[inline]
pub fn intersection(&self, other: &Self) -> Option<Self> {
let start = self.start.max(other.start);
let end = self.end.min(other.end);
if start < end {
Some(Self { start, end })
} else {
None
}
}
#[inline]
pub const fn offset(&self, amount: i32) -> Self {
Self {
start: (self.start as i32 + amount) as u32,
end: (self.end as i32 + amount) as u32,
}
}
#[inline]
pub const fn extend(&self, amount: u32) -> Self {
Self {
start: self.start.saturating_sub(amount),
end: self.end.saturating_add(amount),
}
}
}
impl std::fmt::Display for SourceRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
#[derive(Debug, Clone, Default)]
pub struct SourceMapping {
pub source: SourceRange,
pub generated: SourceRange,
pub data: Option<MappingData>,
}
impl SourceMapping {
#[inline]
pub fn new(source: SourceRange, generated: SourceRange) -> Self {
Self {
source,
generated,
data: None,
}
}
#[inline]
pub fn with_data(source: SourceRange, generated: SourceRange, data: MappingData) -> Self {
Self {
source,
generated,
data: Some(data),
}
}
#[inline]
pub fn contains_source(&self, offset: u32) -> bool {
self.source.contains(offset)
}
#[inline]
pub fn contains_generated(&self, offset: u32) -> bool {
self.generated.contains(offset)
}
pub fn source_to_generated(&self, source_offset: u32) -> Option<u32> {
if self.source.contains(source_offset) {
let relative = source_offset - self.source.start;
let gen_offset =
self.generated.start + relative.min(self.generated.len().saturating_sub(1));
Some(gen_offset)
} else {
None
}
}
pub fn generated_to_source(&self, gen_offset: u32) -> Option<u32> {
if self.generated.contains(gen_offset) {
let relative = gen_offset - self.generated.start;
let src_offset = self.source.start + relative.min(self.source.len().saturating_sub(1));
Some(src_offset)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub enum MappingData {
Expression { text: String },
Directive { name: String, expr: String },
Event { name: String, handler: String },
Binding { prop: String, value: String },
Component { name: String },
Slot { name: String },
Import { source: String, specifier: String },
}
#[derive(Debug, Clone, Default)]
pub struct SourceMap {
mappings: Vec<SourceMapping>,
pub block_offset: u32,
}
impl SourceMap {
#[inline]
pub fn new() -> Self {
Self::default()
}
pub fn from_mappings(mut mappings: Vec<SourceMapping>) -> Self {
mappings.sort_by_key(|m| m.source.start);
Self {
mappings,
block_offset: 0,
}
}
#[inline]
pub fn set_block_offset(&mut self, offset: u32) {
self.block_offset = offset;
}
pub fn add(&mut self, mapping: SourceMapping) {
self.mappings.push(mapping);
self.mappings.sort_by_key(|m| m.source.start);
}
pub fn add_simple(&mut self, source_start: u32, source_end: u32, gen_start: u32, gen_end: u32) {
self.add(SourceMapping::new(
SourceRange::new(source_start, source_end),
SourceRange::new(gen_start, gen_end),
));
}
#[inline]
pub fn mappings(&self) -> &[SourceMapping] {
&self.mappings
}
pub fn to_generated(&self, source_offset: u32) -> Option<u32> {
let idx = self
.mappings
.binary_search_by(|m| {
if m.source.end <= source_offset {
std::cmp::Ordering::Less
} else if m.source.start > source_offset {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
}
})
.ok()?;
self.mappings.get(idx)?.source_to_generated(source_offset)
}
pub fn to_source(&self, gen_offset: u32) -> Option<u32> {
for mapping in &self.mappings {
if let Some(src) = mapping.generated_to_source(gen_offset) {
return Some(src + self.block_offset);
}
}
None
}
pub fn find_by_source(&self, offset: u32) -> Vec<&SourceMapping> {
self.mappings
.iter()
.filter(|m| m.contains_source(offset))
.collect()
}
pub fn find_by_generated(&self, offset: u32) -> Vec<&SourceMapping> {
self.mappings
.iter()
.filter(|m| m.contains_generated(offset))
.collect()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.mappings.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.mappings.len()
}
}
#[cfg(test)]
mod tests {
use super::{SourceMap, SourceMapping, SourceRange};
#[test]
fn test_source_range_contains() {
let range = SourceRange::new(10, 20);
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(15));
assert!(range.contains(19));
assert!(!range.contains(20));
}
#[test]
fn test_source_range_intersects() {
let a = SourceRange::new(10, 20);
let b = SourceRange::new(15, 25);
let c = SourceRange::new(25, 30);
assert!(a.intersects(&b));
assert!(b.intersects(&a));
assert!(!a.intersects(&c));
assert!(!c.intersects(&a));
}
#[test]
fn test_mapping_source_to_generated() {
let mapping = SourceMapping::new(SourceRange::new(10, 20), SourceRange::new(100, 110));
assert_eq!(mapping.source_to_generated(10), Some(100));
assert_eq!(mapping.source_to_generated(15), Some(105));
assert_eq!(mapping.source_to_generated(19), Some(109));
assert_eq!(mapping.source_to_generated(9), None);
assert_eq!(mapping.source_to_generated(20), None);
}
#[test]
fn test_source_map() {
let mut map = SourceMap::new();
map.add_simple(10, 20, 100, 110);
map.add_simple(30, 40, 200, 210);
assert_eq!(map.to_generated(15), Some(105));
assert_eq!(map.to_generated(35), Some(205));
assert_eq!(map.to_generated(25), None);
assert_eq!(map.to_source(105), Some(15));
assert_eq!(map.to_source(205), Some(35));
}
}