nginx_lint_parser/
line_index.rs1use crate::ast::{Position, Span};
8
9pub struct LineIndex {
15 line_starts: Vec<usize>,
17}
18
19impl LineIndex {
20 pub fn new(source: &str) -> Self {
22 let mut line_starts = vec![0usize];
23 for (i, ch) in source.char_indices() {
24 if ch == '\n' {
25 line_starts.push(i + 1);
26 }
27 }
28 Self { line_starts }
29 }
30
31 pub fn position(&self, offset: usize) -> Position {
33 let line_idx = match self.line_starts.binary_search(&offset) {
35 Ok(exact) => exact, Err(ins) => ins - 1, };
38 let col = offset - self.line_starts[line_idx];
39 Position::new(line_idx + 1, col + 1, offset)
40 }
41
42 pub fn span(&self, range: rowan::TextRange) -> Span {
44 let start: usize = range.start().into();
45 let end: usize = range.end().into();
46 Span::new(self.position(start), self.position(end))
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn single_line() {
56 let idx = LineIndex::new("listen 80;");
57 assert_eq!(idx.position(0), Position::new(1, 1, 0));
58 assert_eq!(idx.position(7), Position::new(1, 8, 7));
59 }
60
61 #[test]
62 fn multi_line() {
63 let src = "http {\n listen 80;\n}\n";
64 let idx = LineIndex::new(src);
65 assert_eq!(idx.position(0), Position::new(1, 1, 0));
67 assert_eq!(idx.position(7), Position::new(2, 1, 7));
69 assert_eq!(idx.position(11), Position::new(2, 5, 11)); assert_eq!(idx.position(22), Position::new(3, 1, 22));
72 }
73
74 #[test]
75 fn span_conversion() {
76 let src = "listen 80;";
77 let idx = LineIndex::new(src);
78 let range = rowan::TextRange::new(0.into(), 6.into());
79 let span = idx.span(range);
80 assert_eq!(span.start, Position::new(1, 1, 0));
81 assert_eq!(span.end, Position::new(1, 7, 6));
82 }
83}