use std::{
cmp::Ordering,
iter::FusedIterator,
ops::{Bound, RangeBounds},
};
use oxc_span::Span;
use crate::ast::comment::*;
pub fn comments_range<R>(comments: &[Comment], range: R) -> CommentsRange<'_>
where
R: RangeBounds<u32>,
{
CommentsRange::new(comments, range.start_bound().cloned(), range.end_bound().cloned())
}
pub fn has_comments_between(comments: &[Comment], span: Span) -> bool {
comments_range(comments, span.start..span.end).count() > 0
}
pub fn is_inside_comment(comments: &[Comment], pos: u32) -> bool {
get_comment_at(comments, pos).is_some()
}
pub fn get_comment_at(comments: &[Comment], pos: u32) -> Option<&Comment> {
comments
.binary_search_by(|c| {
if pos < c.span.start {
Ordering::Greater
} else if pos >= c.span.end {
Ordering::Less
} else {
Ordering::Equal
}
})
.ok()
.map(|idx| &comments[idx])
}
pub struct CommentsRange<'c> {
comments: &'c [Comment],
range: (Bound<u32>, Bound<u32>),
current_start: usize,
current_end: usize,
}
impl<'c> CommentsRange<'c> {
fn new(comments: &'c [Comment], start: Bound<u32>, end: Bound<u32>) -> Self {
let partition_start = {
let range_start = match start {
Bound::Unbounded => 0,
Bound::Included(x) => x,
Bound::Excluded(x) => x.saturating_add(1),
};
comments.partition_point(|comment| comment.span.start < range_start)
};
let partition_end = {
let range_end = match end {
Bound::Unbounded => u32::MAX,
Bound::Included(x) => x,
Bound::Excluded(x) => x.saturating_sub(1),
};
comments.partition_point(|comment| comment.span.start <= range_end)
};
Self {
comments,
range: (start, end),
current_start: partition_start,
current_end: partition_end,
}
}
}
impl<'c> Iterator for CommentsRange<'c> {
type Item = &'c Comment;
fn next(&mut self) -> Option<Self::Item> {
if self.current_start < self.current_end {
for comment in &self.comments[self.current_start..self.current_end] {
self.current_start = self.current_start.saturating_add(1);
if self.range.contains(&comment.span.start) {
return Some(comment);
}
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
let max_remaining = self.current_end.saturating_sub(self.current_start);
(0, Some(max_remaining))
}
}
impl DoubleEndedIterator for CommentsRange<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.current_start < self.current_end {
for comment in self.comments[self.current_start..self.current_end].iter().rev() {
self.current_end = self.current_end.saturating_sub(1);
if self.range.contains(&comment.span.start) {
return Some(comment);
}
}
}
None
}
}
impl FusedIterator for CommentsRange<'_> {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_comments_range() {
let comments = vec![
Comment::new(0, 4, CommentKind::Line),
Comment::new(5, 9, CommentKind::Line),
Comment::new(10, 13, CommentKind::Line),
Comment::new(14, 17, CommentKind::Line),
Comment::new(18, 23, CommentKind::Line),
]
.into_boxed_slice();
let full_len = comments.len();
assert_eq!(comments_range(&comments, ..).count(), full_len);
assert_eq!(comments_range(&comments, 1..).count(), full_len.saturating_sub(1));
assert_eq!(comments_range(&comments, ..18).count(), full_len.saturating_sub(1));
assert_eq!(comments_range(&comments, ..=18).count(), full_len);
}
#[test]
fn test_is_inside_comment() {
let comments = vec![
Comment::new(0, 4, CommentKind::Line),
Comment::new(10, 20, CommentKind::SingleLineBlock),
]
.into_boxed_slice();
assert!(is_inside_comment(&comments, 2));
assert!(!is_inside_comment(&comments, 5));
assert!(is_inside_comment(&comments, 15));
assert!(!is_inside_comment(&comments, 21));
}
#[test]
fn test_get_comment_at() {
let comments = vec![
Comment::new(0, 4, CommentKind::Line),
Comment::new(10, 20, CommentKind::SingleLineBlock),
]
.into_boxed_slice();
let comment = get_comment_at(&comments, 2);
let comment = comment.expect("Expected a comment at position 2");
assert_eq!(comment.span.start, 0);
assert_eq!(comment.span.end, 4);
assert!(get_comment_at(&comments, 5).is_none());
let comment = get_comment_at(&comments, 15);
let comment = comment.expect("Expected a comment at position 15");
assert_eq!(comment.span.start, 10);
assert_eq!(comment.span.end, 20);
assert!(get_comment_at(&comments, 21).is_none());
assert!(get_comment_at(&comments, 0).is_some()); assert!(get_comment_at(&comments, 10).is_some());
assert!(get_comment_at(&comments, 4).is_none()); assert!(get_comment_at(&comments, 20).is_none()); }
}