Skip to content

Commit 1798a7a

Browse files
committed
Implemented collapse_comments and tests
1 parent de99a74 commit 1798a7a

1 file changed

Lines changed: 142 additions & 24 deletions

File tree

src/comments.rs

Lines changed: 142 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::fmt;
1010
use crate::ast::ParsedSqlFile;
1111

1212
/// Represents a line/column location within a source file.
13-
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)]
13+
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
1414
pub struct Location {
1515
line: u64,
1616
column: u64,
@@ -47,7 +47,7 @@ impl Default for Location {
4747
}
4848

4949
/// Represents a start/end span (inclusive/exclusive as used by this crate) for a comment in a file.
50-
#[derive(Clone, Debug, Eq, PartialEq)]
50+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5151
pub struct Span {
5252
start: Location,
5353
end: Location,
@@ -361,35 +361,62 @@ impl Comments {
361361
/// - An `u64` value representing the desired line to check above.
362362
/// - [`LeadingCommentCapture`] preference
363363
#[must_use]
364-
pub fn leading_comments(&self, line: u64, capture: &LeadingCommentCapture) -> Vec<&Comment> {
365-
let mut comments = Vec::new();
364+
pub fn leading_comments(&self, line: u64, capture: LeadingCommentCapture) -> Self {
365+
let mut comments: Vec<Comment> = Vec::new();
366366
let mut current_line = line;
367367
let mut seen_multiline = false;
368368
while let Some(leading_comment) = self.leading_comment(current_line) {
369369
match capture {
370370
LeadingCommentCapture::SingleNearest => {
371-
comments.push(leading_comment);
371+
comments.push(leading_comment.to_owned());
372372
break;
373373
}
374-
LeadingCommentCapture::AllLeading => comments.push(leading_comment),
374+
LeadingCommentCapture::AllLeading => comments.push(leading_comment.to_owned()),
375375
LeadingCommentCapture::AllSingleOneMulti => match leading_comment.kind() {
376376
CommentKind::MultiLine if seen_multiline => break,
377377
CommentKind::MultiLine => {
378378
seen_multiline = true;
379-
comments.push(leading_comment);
379+
comments.push(leading_comment.to_owned());
380380
}
381-
CommentKind::SingleLine => comments.push(leading_comment),
381+
CommentKind::SingleLine => comments.push(leading_comment.to_owned()),
382382
},
383383
}
384384
current_line = leading_comment.span().start().line();
385385
}
386386
comments.reverse();
387-
comments
387+
Self::new(comments)
388+
}
389+
390+
/// Collapse this collection separate with `\n` into a single a single comment.
391+
#[must_use]
392+
pub fn collapse_comments(self) -> Option<Comment> {
393+
let mut iter = self.comments.into_iter();
394+
let first = iter.next()?;
395+
396+
let Some(second) = iter.next() else {
397+
return Some(first);
398+
};
399+
400+
let start = *first.span().start();
401+
402+
let mut text = first.text().to_owned();
403+
text.push('\n');
404+
text.push_str(second.text());
405+
406+
let mut end = *second.span().end();
407+
408+
for c in iter {
409+
text.push('\n');
410+
text.push_str(c.text());
411+
end = *c.span().end();
412+
}
413+
414+
Some(Comment::new(text, CommentKind::MultiLine, Span::new(start, end)))
388415
}
389416
}
390417

391418
/// Controls how leading comments are captured for a statement.
392-
#[derive(Default)]
419+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
393420
pub enum LeadingCommentCapture {
394421
/// Capture only the single nearest leading comment.
395422
#[default]
@@ -816,8 +843,8 @@ CREATE TABLE posts (
816843

817844
use crate::comments::LeadingCommentCapture;
818845

819-
fn texts(v: Vec<&Comment>) -> Vec<String> {
820-
v.into_iter().map(|c| c.text().to_owned()).collect()
846+
fn texts(v: &Comments) -> Vec<String> {
847+
v.comments().iter().map(|c| c.text().to_owned()).collect()
821848
}
822849

823850
#[test]
@@ -837,11 +864,11 @@ CREATE TABLE posts (
837864
CREATE TABLE t (id INTEGER);
838865
";
839866
let parsed = Comments::scan_comments(src)?;
840-
let single = parsed.leading_comments(3, &LeadingCommentCapture::SingleNearest);
841-
assert_eq!(texts(single), vec!["c2".to_owned()]);
867+
let single = parsed.leading_comments(3, LeadingCommentCapture::SingleNearest);
868+
assert_eq!(texts(&single), vec!["c2".to_owned()]);
842869

843-
let all = parsed.leading_comments(3, &LeadingCommentCapture::AllLeading);
844-
assert_eq!(texts(all), vec!["c1".to_owned(), "c2".to_owned()]);
870+
let all = parsed.leading_comments(3, LeadingCommentCapture::AllLeading);
871+
assert_eq!(texts(&all), vec!["c1".to_owned(), "c2".to_owned()]);
845872

846873
Ok(())
847874
}
@@ -856,8 +883,8 @@ CREATE TABLE t (id INTEGER);
856883
CREATE TABLE t (id INTEGER);
857884
";
858885
let parsed = Comments::scan_comments(src)?;
859-
let all = parsed.leading_comments(4, &LeadingCommentCapture::AllLeading);
860-
assert_eq!(texts(all), vec!["c2".to_owned()]);
886+
let all = parsed.leading_comments(4, LeadingCommentCapture::AllLeading);
887+
assert_eq!(texts(&all), vec!["c2".to_owned()]);
861888

862889
Ok(())
863890
}
@@ -873,8 +900,8 @@ m */
873900
CREATE TABLE t (id INTEGER);
874901
";
875902
let parsed = Comments::scan_comments(src)?;
876-
let got = parsed.leading_comments(5, &LeadingCommentCapture::AllSingleOneMulti);
877-
assert_eq!(texts(got), vec!["m\nm".to_owned(), "s1".to_owned(), "s2".to_owned(),]);
903+
let got = parsed.leading_comments(5, LeadingCommentCapture::AllSingleOneMulti);
904+
assert_eq!(texts(&got), vec!["m\nm".to_owned(), "s1".to_owned(), "s2".to_owned(),]);
878905

879906
Ok(())
880907
}
@@ -889,8 +916,8 @@ CREATE TABLE t (id INTEGER);
889916
CREATE TABLE t (id INTEGER);
890917
";
891918
let parsed = Comments::scan_comments(src)?;
892-
let got = parsed.leading_comments(4, &LeadingCommentCapture::AllSingleOneMulti);
893-
assert_eq!(texts(got), vec!["m2".to_owned(), "s1".to_owned()]);
919+
let got = parsed.leading_comments(4, LeadingCommentCapture::AllSingleOneMulti);
920+
assert_eq!(texts(&got), vec!["m2".to_owned(), "s1".to_owned()]);
894921

895922
Ok(())
896923
}
@@ -904,8 +931,99 @@ world */
904931
CREATE TABLE t (id INTEGER);
905932
";
906933
let parsed = Comments::scan_comments(src)?;
907-
let got = parsed.leading_comments(3, &LeadingCommentCapture::SingleNearest);
908-
assert_eq!(texts(got), vec!["hello\nworld".to_owned()]);
934+
let got = parsed.leading_comments(3, LeadingCommentCapture::SingleNearest);
935+
assert_eq!(texts(&got), vec!["hello\nworld".to_owned()]);
936+
937+
Ok(())
938+
}
939+
940+
#[test]
941+
fn collapse_comments_empty_returns_none() {
942+
let comments = Comments::new(vec![]);
943+
assert!(comments.collapse_comments().is_none());
944+
}
945+
946+
#[test]
947+
fn collapse_comments_single_returns_same_comment() {
948+
let c = Comment::new(
949+
"solo".to_owned(),
950+
CommentKind::SingleLine,
951+
Span::new(Location::new(10, 3), Location::new(10, 11)),
952+
);
953+
let comments = Comments::new(vec![c]);
954+
955+
let collapsed =
956+
comments.collapse_comments().unwrap_or_else(|| panic!("should return a comment"));
957+
assert_eq!(collapsed.text(), "solo");
958+
assert_eq!(collapsed.kind(), &CommentKind::SingleLine);
959+
assert_eq!(collapsed.span(), &Span::new(Location::new(10, 3), Location::new(10, 11)));
960+
}
961+
962+
#[test]
963+
fn collapse_comments_multiple_joins_text_and_expands_span_and_sets_multiline_kind() {
964+
let c1 = Comment::new(
965+
"a".to_owned(),
966+
CommentKind::SingleLine,
967+
Span::new(Location::new(1, 1), Location::new(1, 6)),
968+
);
969+
let c2 = Comment::new(
970+
"b".to_owned(),
971+
CommentKind::SingleLine,
972+
Span::new(Location::new(2, 1), Location::new(2, 6)),
973+
);
974+
let c3 = Comment::new(
975+
"c".to_owned(),
976+
CommentKind::MultiLine,
977+
Span::new(Location::new(3, 1), Location::new(4, 3)),
978+
);
979+
980+
let comments = Comments::new(vec![c1, c2, c3]);
981+
982+
let collapsed = comments.collapse_comments().unwrap_or_else(|| panic!("should collapse"));
983+
assert_eq!(collapsed.text(), "a\nb\nc");
984+
assert_eq!(collapsed.kind(), &CommentKind::MultiLine);
985+
assert_eq!(collapsed.span(), &Span::new(Location::new(1, 1), Location::new(4, 3)));
986+
}
987+
988+
#[test]
989+
fn collapse_comments_with_leading_comments_allleading_collapses_correctly()
990+
-> Result<(), Box<dyn std::error::Error>> {
991+
let src = "\
992+
-- c1
993+
-- c2
994+
CREATE TABLE t (id INTEGER);
995+
";
996+
let parsed = Comments::scan_comments(src)?;
997+
998+
let leading = parsed.leading_comments(3, LeadingCommentCapture::AllLeading);
999+
assert_eq!(texts(&leading), vec!["c1".to_owned(), "c2".to_owned()]);
1000+
1001+
let collapsed = leading.collapse_comments().unwrap_or_else(|| panic!("should collapse"));
1002+
assert_eq!(collapsed.text(), "c1\nc2");
1003+
assert_eq!(collapsed.kind(), &CommentKind::MultiLine);
1004+
1005+
// Span sanity: starts at first comment start, ends at second comment end.
1006+
assert_eq!(*collapsed.span().start(), Location::new(1, 1));
1007+
assert_eq!(collapsed.span().end().line(), 2);
1008+
1009+
Ok(())
1010+
}
1011+
1012+
#[test]
1013+
fn collapse_comments_with_leading_comments_single_nearest_preserves_kind()
1014+
-> Result<(), Box<dyn std::error::Error>> {
1015+
let src = "\
1016+
-- c1
1017+
-- c2
1018+
CREATE TABLE t (id INTEGER);
1019+
";
1020+
let parsed = Comments::scan_comments(src)?;
1021+
let leading = parsed.leading_comments(3, LeadingCommentCapture::SingleNearest);
1022+
assert_eq!(texts(&leading), vec!["c2".to_owned()]);
1023+
1024+
let collapsed = leading.collapse_comments().unwrap_or_else(|| panic!("should collapse"));
1025+
assert_eq!(collapsed.text(), "c2");
1026+
assert_eq!(collapsed.kind(), &CommentKind::SingleLine);
9091027

9101028
Ok(())
9111029
}

0 commit comments

Comments
 (0)