@@ -10,7 +10,7 @@ use std::fmt;
1010use 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 ) ]
1414pub 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 ) ]
5151pub 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 ) ]
393420pub 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 (
837864CREATE 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);
856883CREATE 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 }
873900CREATE 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\n m" . to_owned( ) , "s1" . to_owned( ) , "s2" . to_owned( ) , ] ) ;
903+ let got = parsed. leading_comments ( 5 , LeadingCommentCapture :: AllSingleOneMulti ) ;
904+ assert_eq ! ( texts( & got) , vec![ "m\n m" . to_owned( ) , "s1" . to_owned( ) , "s2" . to_owned( ) , ] ) ;
878905
879906 Ok ( ( ) )
880907 }
@@ -889,8 +916,8 @@ CREATE TABLE t (id INTEGER);
889916CREATE 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 */
904931CREATE 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\n world" . to_owned( ) ] ) ;
934+ let got = parsed. leading_comments ( 3 , LeadingCommentCapture :: SingleNearest ) ;
935+ assert_eq ! ( texts( & got) , vec![ "hello\n world" . 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\n b\n c" ) ;
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\n c2" ) ;
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