use memchr;
use rspack_core::{DependencyLocation, DependencyRange, RealDependencyLocation, SourcePosition};
#[derive(Debug, Default)]
pub struct DependencyLocationAdvancer {
last_range: Option<DependencyRange>,
last_location: Option<DependencyLocation>,
last_start_pos: Option<SourcePosition>,
}
impl DependencyLocationAdvancer {
pub fn new() -> Self {
Self::default()
}
fn advance_pos(
source: &str,
from_off: usize,
from_pos: SourcePosition,
to_off: usize,
) -> Option<SourcePosition> {
if to_off < from_off || to_off > source.len() {
return None;
}
let segment = &source[from_off..to_off];
let bytes = segment.as_bytes();
let (newline_count, last_newline_idx) = memchr::memchr_iter(b'\n', bytes)
.enumerate()
.last()
.map_or((0, None), |(count, idx)| (count + 1, Some(idx)));
if let Some(last_idx) = last_newline_idx {
let line = from_pos
.line
.checked_add(u32::try_from(newline_count).ok()?)?;
let after_newline = &segment[last_idx + 1..];
let column = u32::try_from(after_newline.encode_utf16().count() + 1).ok()?; Some(SourcePosition { line, column })
} else {
let column_advance = u32::try_from(segment.encode_utf16().count()).ok()?;
Some(SourcePosition {
line: from_pos.line,
column: from_pos.column.checked_add(column_advance)?,
})
}
}
pub fn compute_dependency_location(
&mut self,
source: &str,
range: DependencyRange,
) -> Option<DependencyLocation> {
let start = range.start as usize;
let end = range.end as usize;
if Some(range) == self.last_range {
return self.last_location.clone();
}
let (base_offset, base_pos) = if let (Some(last_range), Some(last_start_pos)) =
(self.last_range, self.last_start_pos)
&& start >= last_range.start as usize
{
(last_range.start as usize, last_start_pos)
} else {
(0, SourcePosition { line: 1, column: 1 })
};
let result = (|| {
let start_pos = Self::advance_pos(source, base_offset, base_pos, start)?;
let end_pos = Self::advance_pos(source, start, start_pos, end)?;
if start_pos.line == end_pos.line && start_pos.column == end_pos.column {
Some(DependencyLocation::Real(RealDependencyLocation::new(
start_pos, None,
)))
} else {
Some(DependencyLocation::Real(RealDependencyLocation::new(
start_pos,
Some(end_pos),
)))
}
})();
if let Some(loc) = &result {
self.last_range = Some(range);
self.last_location = Some(loc.clone());
if let DependencyLocation::Real(real_loc) = loc {
self.last_start_pos = Some(real_loc.start);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_same_range_cache() {
let mut cache = DependencyLocationAdvancer::new();
let source = "import a from './a';\nimport b from './b';";
let range = DependencyRange::new(0, 20);
let loc1 = cache.compute_dependency_location(source, range);
let loc2 = cache.compute_dependency_location(source, range);
assert_eq!(
loc1.as_ref().map(|l| l.to_string()),
loc2.as_ref().map(|l| l.to_string())
);
assert!(loc1.is_some());
}
#[test]
fn test_incremental_calculation() {
let mut cache = DependencyLocationAdvancer::new();
let source = "import a from './a';\nimport b from './b';\nimport c from './c';";
let range1 = DependencyRange::new(0, 20);
let loc1 = cache.compute_dependency_location(source, range1).unwrap();
let range2 = DependencyRange::new(21, 41);
let loc2 = cache.compute_dependency_location(source, range2).unwrap();
if let DependencyLocation::Real(real1) = &loc1 {
assert_eq!(real1.start.line, 1);
}
if let DependencyLocation::Real(real2) = &loc2 {
assert_eq!(real2.start.line, 2);
}
}
#[test]
fn test_fallback_to_full_calculation() {
let mut cache = DependencyLocationAdvancer::new();
let source = "import a from './a';\nimport b from './b';\nimport c from './c';";
let range1 = DependencyRange::new(21, 41);
let loc1 = cache.compute_dependency_location(source, range1);
let range2 = DependencyRange::new(0, 20);
let loc2 = cache.compute_dependency_location(source, range2);
assert!(loc1.is_some());
assert!(loc2.is_some());
if let Some(DependencyLocation::Real(real1)) = &loc1 {
assert_eq!(real1.start.line, 2);
}
if let Some(DependencyLocation::Real(real2)) = &loc2 {
assert_eq!(real2.start.line, 1);
}
}
#[test]
fn test_advance_pos_same_line() {
let source = "hello world";
let from_pos = SourcePosition { line: 1, column: 1 };
let result = DependencyLocationAdvancer::advance_pos(source, 0, from_pos, 5);
assert!(result.is_some());
let pos = result.unwrap();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 6); }
#[test]
fn test_advance_pos_multiline() {
let source = "hello\nworld";
let from_pos = SourcePosition { line: 1, column: 6 };
let result = DependencyLocationAdvancer::advance_pos(source, 5, from_pos, 11);
assert!(result.is_some());
let pos = result.unwrap();
assert_eq!(pos.line, 2);
assert_eq!(pos.column, 6); }
#[test]
fn test_advance_pos_utf8_multibyte() {
let source = "你好世界";
let from_pos = SourcePosition { line: 1, column: 1 };
let result = DependencyLocationAdvancer::advance_pos(source, 0, from_pos, 6);
assert!(result.is_some());
let pos = result.unwrap();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 3); }
#[test]
fn test_advance_pos_emoji() {
let source = "hello😀world";
let from_pos = SourcePosition { line: 1, column: 1 };
let result = DependencyLocationAdvancer::advance_pos(source, 0, from_pos, 9);
assert!(result.is_some());
let pos = result.unwrap();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 8); }
#[test]
fn test_multiple_ranges() {
let mut cache = DependencyLocationAdvancer::new();
let source = "import a from './a';\nimport b from './b';\nimport c from './c';";
let ranges = vec![
DependencyRange::new(0, 20),
DependencyRange::new(21, 41),
DependencyRange::new(42, 62),
];
for range in ranges {
let result = cache.compute_dependency_location(source, range);
assert!(
result.is_some(),
"Should compute location for range {range:?}",
);
if let Some(DependencyLocation::Real(real_loc)) = &result {
assert!(real_loc.start.line > 0, "Line should be 1-based");
assert!(real_loc.start.column > 0, "Column should be 1-based");
}
}
}
#[test]
fn test_empty_source() {
let mut cache = DependencyLocationAdvancer::new();
let source = "";
let range = DependencyRange::new(0, 0);
let result = cache.compute_dependency_location(source, range);
assert!(result.is_some());
}
#[test]
fn test_single_line() {
let mut cache = DependencyLocationAdvancer::new();
let source = "single line";
let range = DependencyRange::new(0, 11);
let result = cache.compute_dependency_location(source, range).unwrap();
if let DependencyLocation::Real(real) = result {
assert_eq!(real.start.line, 1);
assert_eq!(real.end.as_ref().map(|e| e.line), Some(1));
}
}
}