From 813dea1fdd89689d7dfb2c6b68032bc89f8319a0 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Wed, 28 Jan 2026 21:09:01 +0800 Subject: [PATCH 01/11] implemented setters for comment text and spans --- src/comments.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/comments.rs b/src/comments.rs index 642f950..4b8b58c 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -140,6 +140,24 @@ impl Comment { pub fn text(&self) -> &str { self.kind().comment() } + + /// Set the [`CommentKind`] for the comment + pub fn set_kind(&mut self, kind: CommentKind) -> &mut Self { + self.kind = kind; + self + } + + /// Sets new [`Span`] locations for the comment + pub const fn set_span_locations(&mut self, start: Location, end: Location) -> &mut Self { + self.span = Span::new(start, end); + self + } +} + +impl Default for Comment { + fn default() -> Self { + Self { kind: CommentKind::SingleLine(String::new()), span: Span::default() } + } } /// Enum for returning errors withe Comment parsing @@ -363,6 +381,21 @@ impl Comments { } } +fn combine_leading_comments(comments: &Comments, line: u64) -> Comment { + let mut comment: Comment = Comment::default(); + let mut content = String::new(); + let mut span = Span::default(); + for possible_comment in comments.comments() { + if possible_comment.span().end().line() < line { + comment.set_kind(CommentKind::SingleLine( + comment.text().to_owned() + possible_comment.text(), + )); + } + } + + comment +} + #[cfg(test)] mod tests { use std::{env, fs}; From be1f04103f618b647d412bd99df57b43d20f0b69 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Wed, 28 Jan 2026 21:16:40 +0800 Subject: [PATCH 02/11] implemented setters for comment text and spans --- src/comments.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/comments.rs b/src/comments.rs index 4b8b58c..6c3127d 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -102,6 +102,14 @@ impl CommentKind { Self::MultiLine(comment) | Self::SingleLine(comment) => comment, } } + /// Setter method for setting the comment value + pub fn set_comment(&mut self, text: &str) { + match self { + Self::MultiLine(s) | Self::SingleLine(s) => { + *s = text.to_owned(); + } + } + } } /// Structure for containing the [`CommentKind`] and the [`Span`] for a comment From 71c0f95c72c96dc564199a989ba45bc58866a5fc Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 08:43:26 +0800 Subject: [PATCH 03/11] implemented test for `all_valid_leading_comments` --- src/comments.rs | 91 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index 6c3127d..dadcb9b 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -10,7 +10,7 @@ use std::fmt; use crate::ast::ParsedSqlFile; /// Represents a line/column location within a source file. -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)] pub struct Location { line: u64, column: u64, @@ -387,21 +387,52 @@ impl Comments { pub fn leading_comment(&self, line: u64) -> Option<&Comment> { self.comments().iter().rev().find(|comment| comment.span().end().line() + 1 == line) } + + /// Method for retrieving all comments that occur directly before a specified line + /// + /// # Parameters + /// - `self` the current [`Comments`] object + /// - The `line` as a [`u64`] to use for reference + pub fn all_valid_leading_comments(&self, line: u64) -> Vec<&Comment> { + let mut found_comments = Vec::new(); + let mut current_line = match line.checked_sub(1) { + Some(n) => n, + None => return found_comments, + }; + for comment in self.comments().iter().rev() { + let start = comment.span().end().line(); + let end = comment.span().end().line(); + if end == current_line { + found_comments.push(comment); + if current_line == 0 { + break; + } + current_line = start.saturating_sub(1); + } else if comment.span().end().line() < line { + break; + } + } + found_comments.reverse(); + found_comments + } } -fn combine_leading_comments(comments: &Comments, line: u64) -> Comment { - let mut comment: Comment = Comment::default(); +fn combine_leading_comments(comments: Vec<&Comment>) -> Comment { + let mut final_comment: Comment = Comment::default(); + let kind = comments[0].kind(); + final_comment.set_kind(kind.clone()); let mut content = String::new(); - let mut span = Span::default(); - for possible_comment in comments.comments() { - if possible_comment.span().end().line() < line { - comment.set_kind(CommentKind::SingleLine( - comment.text().to_owned() + possible_comment.text(), - )); - } + let start = comments[0].span().start(); + let mut end = Location::default(); + for comment in comments.iter().rev() { + end = *comment.span().end(); + content.push_str(comment.text()); } - comment + final_comment.kind.set_comment(&content); + final_comment.set_span_locations(start.to_owned(), end); + + final_comment } #[cfg(test)] @@ -818,4 +849,42 @@ CREATE TABLE posts ( assert_eq!(comment.span().end(), comment_vec[i].span().end()); } } + + #[test] + fn test_all_valid_leading_comments() { + // Lines: + // 1: -- old + // 2: + // 3: -- a + // 4: -- b + // 5: implied statement + let comment_vec = vec![ + Comment::new( + CommentKind::SingleLine("old".to_owned()), + Span::new(Location::new(1, 1), Location::new(1, 6)), + ), + Comment::new( + CommentKind::SingleLine("a".to_owned()), + Span::new(Location::new(3, 1), Location::new(3, 4)), + ), + Comment::new( + CommentKind::SingleLine("b".to_owned()), + Span::new(Location::new(4, 1), Location::new(4, 4)), + ), + ]; + + // IMPORTANT: use Comments::new to enforce ordering invariants + let comments = Comments::new(comment_vec); + + // Statement is on line 5; leading comment block should be lines 3-4 only. + let leading = comments.all_valid_leading_comments(5); + + assert_eq!(leading.len(), 2); + assert_eq!(leading[0].text(), "a"); + assert_eq!(leading[1].text(), "b"); + + // Also confirm the spans align to those lines. + assert_eq!(leading[0].span().start().line(), 3); + assert_eq!(leading[1].span().end().line(), 4); + } } From 6199f1ee0e057d7e6e5104b639981897177193c7 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 08:44:31 +0800 Subject: [PATCH 04/11] implemented test for `all_valid_leading_comments` --- src/comments.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index dadcb9b..9283acf 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -873,10 +873,8 @@ CREATE TABLE posts ( ), ]; - // IMPORTANT: use Comments::new to enforce ordering invariants let comments = Comments::new(comment_vec); - // Statement is on line 5; leading comment block should be lines 3-4 only. let leading = comments.all_valid_leading_comments(5); assert_eq!(leading.len(), 2); From adfc8b5bb5eab6adf814ae79ccd78f5355359766 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 08:44:38 +0800 Subject: [PATCH 05/11] implemented test for `all_valid_leading_comments` --- src/comments.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/comments.rs b/src/comments.rs index 9283acf..085e5e5 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -881,7 +881,6 @@ CREATE TABLE posts ( assert_eq!(leading[0].text(), "a"); assert_eq!(leading[1].text(), "b"); - // Also confirm the spans align to those lines. assert_eq!(leading[0].span().start().line(), 3); assert_eq!(leading[1].span().end().line(), 4); } From 4c6cbcb81ec82064a42e46e9700566d10873305c Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 10:56:30 +0800 Subject: [PATCH 06/11] Removed enum for CommentKind --- src/comments.rs | 190 +++++++++--------------------------------------- 1 file changed, 35 insertions(+), 155 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index 085e5e5..801d594 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -84,38 +84,12 @@ impl Default for Span { } } -/// Enum for holding the comment content, differentiated by single line `--` and -/// multiline `/* */` -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum CommentKind { - /// Enum variant for Multiline Comments - MultiLine(String), - /// Enum variant for Single Line Comments - SingleLine(String), -} - -impl CommentKind { - /// Getter for returning the comment from within the enum - #[must_use] - pub fn comment(&self) -> &str { - match self { - Self::MultiLine(comment) | Self::SingleLine(comment) => comment, - } - } - /// Setter method for setting the comment value - pub fn set_comment(&mut self, text: &str) { - match self { - Self::MultiLine(s) | Self::SingleLine(s) => { - *s = text.to_owned(); - } - } - } -} - -/// Structure for containing the [`CommentKind`] and the [`Span`] for a comment -#[derive(Clone, Debug, Eq, PartialEq)] +/// Structure for containing the comment, location and style +#[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct Comment { - kind: CommentKind, + /// The comment content found as a [`String`] + text: String, + /// The location of the comment start/finish as a [`Span`] span: Span, } @@ -123,17 +97,11 @@ impl Comment { /// Method for making a new comment /// /// # Parameters - /// - `kind` where the type of comment is passed as a [`CommentKind`] + /// - `text` the text of the comment as a [`String`] /// - `span` where the [`Span`] of the comment is passed #[must_use] - pub const fn new(kind: CommentKind, span: Span) -> Self { - Self { kind, span } - } - - /// Getter method to get the [`CommentKind`] - #[must_use] - pub const fn kind(&self) -> &CommentKind { - &self.kind + pub const fn new(text: String, span: Span) -> Self { + Self { text, span } } /// Getter method to get the [`Span`] of the comment @@ -142,29 +110,25 @@ impl Comment { &self.span } - /// Getter method that will return the comment content as a [`str`], - /// regardless of [`CommentKind`] + /// Getter method that will return the comment content as a &[`str`] #[must_use] pub fn text(&self) -> &str { - self.kind().comment() + &self.text } - /// Set the [`CommentKind`] for the comment - pub fn set_kind(&mut self, kind: CommentKind) -> &mut Self { - self.kind = kind; - self + /// Setter for text of comment + pub fn set_text(&mut self, text: impl Into) { + self.text = text.into(); } - /// Sets new [`Span`] locations for the comment - pub const fn set_span_locations(&mut self, start: Location, end: Location) -> &mut Self { - self.span = Span::new(start, end); - self + /// Mutator method to update comment text + pub(crate) const fn text_mut(&mut self) -> &mut String { + &mut self.text } -} -impl Default for Comment { - fn default() -> Self { - Self { kind: CommentKind::SingleLine(String::new()), span: Span::default() } + /// Sets new [`Span`] locations for the comment + pub const fn set_span_locations(&mut self, start: Location, end: Location) { + self.span = Span::new(start, end); } } @@ -325,7 +289,7 @@ impl Comments { }) .collect(); comments.push(Comment::new( - CommentKind::MultiLine(normalized_comment), + normalized_comment, Span::new( Location { line: start_line, column: start_col }, end_loc, @@ -352,7 +316,7 @@ impl Comments { in_single = false; let end_loc = Location::new(line_num, col); comments.push(Comment::new( - CommentKind::SingleLine(buf.trim().to_owned()), + buf.trim().to_owned(), Span::new(Location { line: start_line, column: start_col }, end_loc), )); buf.clear(); @@ -387,59 +351,13 @@ impl Comments { pub fn leading_comment(&self, line: u64) -> Option<&Comment> { self.comments().iter().rev().find(|comment| comment.span().end().line() + 1 == line) } - - /// Method for retrieving all comments that occur directly before a specified line - /// - /// # Parameters - /// - `self` the current [`Comments`] object - /// - The `line` as a [`u64`] to use for reference - pub fn all_valid_leading_comments(&self, line: u64) -> Vec<&Comment> { - let mut found_comments = Vec::new(); - let mut current_line = match line.checked_sub(1) { - Some(n) => n, - None => return found_comments, - }; - for comment in self.comments().iter().rev() { - let start = comment.span().end().line(); - let end = comment.span().end().line(); - if end == current_line { - found_comments.push(comment); - if current_line == 0 { - break; - } - current_line = start.saturating_sub(1); - } else if comment.span().end().line() < line { - break; - } - } - found_comments.reverse(); - found_comments - } -} - -fn combine_leading_comments(comments: Vec<&Comment>) -> Comment { - let mut final_comment: Comment = Comment::default(); - let kind = comments[0].kind(); - final_comment.set_kind(kind.clone()); - let mut content = String::new(); - let start = comments[0].span().start(); - let mut end = Location::default(); - for comment in comments.iter().rev() { - end = *comment.span().end(); - content.push_str(comment.text()); - } - - final_comment.kind.set_comment(&content); - final_comment.set_span_locations(start.to_owned(), end); - - final_comment } #[cfg(test)] mod tests { use std::{env, fs}; - use crate::comments::{Comment, CommentError, CommentKind, Comments, Location, Span}; + use crate::comments::{Comment, CommentError, Comments, Location, Span}; #[test] fn location_new_and_default() { @@ -470,13 +388,13 @@ mod tests { let raw_comment = "-- a comment"; let len = raw_comment.len() as u64; - let singleline = CommentKind::SingleLine(raw_comment.to_owned()); + let singleline = raw_comment.to_owned(); let mut span = Span::default(); span.end.column = len - 1; let comment = Comment::new(singleline.clone(), span); - assert_eq!(comment.kind, singleline); + assert_eq!(comment.text(), singleline); let expected_span = Span::new(Location { line: 1, column: 1 }, Location { line: 1, column: len - 1 }); @@ -486,12 +404,12 @@ mod tests { #[test] fn multiline_comment_span() { - let kind = CommentKind::MultiLine("/* hello world */".to_owned()); + let text = "/* hello world */".to_owned(); let span = Span::new(Location { line: 1, column: 1 }, Location { line: 2, column: 9 }); - let comment = Comment::new(kind.clone(), span); + let comment = Comment::new(text.clone(), span); - assert_eq!(comment.kind, kind); + assert_eq!(comment.text(), text); assert_eq!(comment.span.start.line, 1); assert_eq!(comment.span.end.line, 2); } @@ -561,7 +479,7 @@ mod tests { ); for (i, comment) in comments.iter().enumerate() { - assert_eq!(expected[i], comment.kind().comment(), "comment at index {i} did not match"); + assert_eq!(expected[i], comment.text(), "comment at index {i} did not match"); } } @@ -755,11 +673,11 @@ CREATE TABLE posts ( let comments = comments.comments(); assert_eq!(comments.len(), 11); let first = &comments[0]; - assert_eq!(first.kind().comment(), "Users table stores user account information"); + assert_eq!(first.text(), "Users table stores user account information"); assert_eq!(first.span().start(), &Location::new(1, 1)); assert_eq!(first.span().end(), &Location::new(1, 47)); let primary_key = &comments[1]; - assert_eq!(primary_key.kind().comment(), "Primary key"); + assert_eq!(primary_key.text(), "Primary key"); assert_eq!(primary_key.span().start(), &Location::new(3, 5)); assert_eq!(primary_key.span().end(), &Location::new(3, 19)); assert!( @@ -793,10 +711,7 @@ CREATE TABLE posts ( let comments = comments.comments(); assert_eq!(comments.len(), 11); let first = &comments[0]; - assert_eq!( - first.kind().comment(), - "Users table stores user account information\nmultiline" - ); + assert_eq!(first.text(), "Users table stores user account information\nmultiline"); assert_eq!(first.span().start(), &Location::new(1, 1)); assert_eq!(first.span().end().line(), 2); assert!( @@ -804,7 +719,7 @@ CREATE TABLE posts ( "end column should be after start column for first multiline comment", ); let primary_key = &comments[1]; - assert_eq!(primary_key.kind().comment(), "Primary key\nmultiline"); + assert_eq!(primary_key.text(), "Primary key\nmultiline"); assert_eq!(primary_key.span().start(), &Location::new(4, 5)); assert_eq!(primary_key.span().end().line(), 5); assert!( @@ -832,11 +747,11 @@ CREATE TABLE posts ( fn test_comments() { let comment_vec = vec![ Comment::new( - CommentKind::SingleLine("a comment".to_owned()), + "a comment".to_owned(), Span { start: Location::new(1, 1), end: Location::new(1, 12) }, ), Comment::new( - CommentKind::SingleLine("a second comment".to_owned()), + "a second comment".to_owned(), Span { start: Location::new(1, 1), end: Location::new(2, 19) }, ), ]; @@ -844,44 +759,9 @@ CREATE TABLE posts ( let comments = Comments::new(comment_vec.clone()); assert!(comments.comments().len() == length); for (i, comment) in comments.comments().iter().enumerate() { - assert_eq!(comment.kind().comment(), comment_vec[i].kind().comment()); + assert_eq!(comment.text(), comment_vec[i].text()); assert_eq!(comment.span().start(), comment_vec[i].span().start()); assert_eq!(comment.span().end(), comment_vec[i].span().end()); } } - - #[test] - fn test_all_valid_leading_comments() { - // Lines: - // 1: -- old - // 2: - // 3: -- a - // 4: -- b - // 5: implied statement - let comment_vec = vec![ - Comment::new( - CommentKind::SingleLine("old".to_owned()), - Span::new(Location::new(1, 1), Location::new(1, 6)), - ), - Comment::new( - CommentKind::SingleLine("a".to_owned()), - Span::new(Location::new(3, 1), Location::new(3, 4)), - ), - Comment::new( - CommentKind::SingleLine("b".to_owned()), - Span::new(Location::new(4, 1), Location::new(4, 4)), - ), - ]; - - let comments = Comments::new(comment_vec); - - let leading = comments.all_valid_leading_comments(5); - - assert_eq!(leading.len(), 2); - assert_eq!(leading[0].text(), "a"); - assert_eq!(leading[1].text(), "b"); - - assert_eq!(leading[0].span().start().line(), 3); - assert_eq!(leading[1].span().end().line(), 4); - } } From 871e56b4d501080c24a39643f05e1c2d9f8371ee Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 13:05:53 +0800 Subject: [PATCH 07/11] worked on updating `scan_comments()` --- src/comments.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/comments.rs b/src/comments.rs index 801d594..de2ae66 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -246,7 +246,16 @@ impl Comments { for line in src.lines() { col = 1; let mut chars = line.chars().peekable(); + + let t = line.trim_start(); + let starts_comment = t.starts_with("--") || t.starts_with("/*"); + if !(starts_comment) { + buf.clear(); + } + + while let Some(c) = chars.next() { + match (in_single, in_multi, c) { (false, false, '-') => { if chars.peek().copied() == Some('-') { From 0b0d3490522ad0c2b4e72f167dcecf7b274d4ef9 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Thu, 29 Jan 2026 21:04:55 +0800 Subject: [PATCH 08/11] improved testing fail feedback --- src/comments.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index de2ae66..e1a7884 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -253,9 +253,7 @@ impl Comments { buf.clear(); } - while let Some(c) = chars.next() { - match (in_single, in_multi, c) { (false, false, '-') => { if chars.peek().copied() == Some('-') { @@ -445,7 +443,13 @@ mod tests { let parsed_set = ParsedSqlFileSet::parse_all(set)?; for file in parsed_set.files() { - let parsed_comments = Comments::parse_all_comments_from_file(file)?; + let parsed_comments = Comments::parse_all_comments_from_file(file).map_err(|e| { + format!( + "parse_all_comments_from_file failed for file {:?}: {e}", + file.file().path() + ) + })?; + let filename = file .file() .path() From 6afb231c0ac57d9fe64145c3fa206d2ff6c9be18 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Fri, 30 Jan 2026 12:17:03 +0800 Subject: [PATCH 09/11] updated `scan_comments` for new sequential comments. Still needs tests --- src/comments.rs | 96 ++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index e1a7884..3cffde4 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -121,11 +121,6 @@ impl Comment { self.text = text.into(); } - /// Mutator method to update comment text - pub(crate) const fn text_mut(&mut self) -> &mut String { - &mut self.text - } - /// Sets new [`Span`] locations for the comment pub const fn set_span_locations(&mut self, start: Location, end: Location) { self.span = Span::new(start, end); @@ -236,32 +231,32 @@ impl Comments { let mut start_col = 1u64; let mut line_num = 1u64; - let mut col; let mut in_single = false; let mut in_multi = false; + let lines: Vec<&str> = src.lines().collect(); + let mut buf = String::new(); - for line in src.lines() { - col = 1; + for (i, line) in lines.iter().enumerate() { + let mut col = 1; let mut chars = line.chars().peekable(); - - let t = line.trim_start(); - let starts_comment = t.starts_with("--") || t.starts_with("/*"); - if !(starts_comment) { - buf.clear(); - } - while let Some(c) = chars.next() { match (in_single, in_multi, c) { (false, false, '-') => { if chars.peek().copied() == Some('-') { chars.next(); in_single = true; - start_line = line_num; - start_col = col; - buf.clear(); + let continuing = i + .checked_sub(1) + .and_then(|j| lines.get(j)) + .is_some_and(|prev| prev.trim().starts_with("--")); + if !continuing { + buf.clear(); + start_line = line_num; + start_col = col; + } col += 1; } } @@ -277,37 +272,30 @@ impl Comments { } (false, false, '*') => { if chars.peek().copied() == Some('/') { - let loc = Location::new(line_num, col); return Err(CommentError::UnmatchedMultilineCommentStart { - location: loc, + location: Location::new(line_num, col), }); } } (false, true, '*') => { - if chars.peek().copied() == Some('/') { - chars.next(); - let end_loc = Location::new(line_num, col + 1); - let normalized_comment = buf - .lines() - .enumerate() - .map(|(i, line)| match i { - 0 => line.trim().to_owned(), - _ => "\n".to_owned() + line.trim(), - }) - .collect(); - comments.push(Comment::new( - normalized_comment, - Span::new( - Location { line: start_line, column: start_col }, - end_loc, - ), - )); - in_multi = false; - buf.clear(); - col += 1; - } else { + if chars.peek().copied() != Some('/') { buf.push('*'); + continue; } + chars.next(); + let normalized_comment = + buf.lines().map(str::trim).collect::>().join("\n"); + + comments.push(Comment::new( + normalized_comment, + Span::new( + Location { line: start_line, column: start_col }, + Location::new(line_num, col + 1), + ), + )); + in_multi = false; + buf.clear(); + col += 1; } (false, true, ch) | (true, false, ch) => { buf.push(ch); @@ -321,23 +309,23 @@ impl Comments { } if in_single { in_single = false; - let end_loc = Location::new(line_num, col); - comments.push(Comment::new( - buf.trim().to_owned(), - Span::new(Location { line: start_line, column: start_col }, end_loc), - )); - buf.clear(); + if lines.get(i + 1).is_some_and(|line| line.trim().starts_with("--")) { + buf.push('\n'); + } else { + comments.push(Comment::new( + buf.trim().to_owned(), + Span::new( + Location { line: start_line, column: start_col }, + Location::new(line_num, col), + ), + )); + buf.clear(); + } } else if in_multi { buf.push('\n'); } line_num += 1; } - // EOF: close any open comments - if in_multi { - return Err(CommentError::UnterminatedMultiLineComment { - start: Location { line: start_line, column: start_col }, - }); - } Ok(Self { comments }) } From 86f52fd2f725c4e6b98847c63e9df7fae56fbf2d Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Fri, 30 Jan 2026 13:06:27 +0800 Subject: [PATCH 10/11] added passing tests for combining sequential singl lines; fixes #20 --- src/comments.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/comments.rs b/src/comments.rs index 3cffde4..e08db8d 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -313,7 +313,7 @@ impl Comments { buf.push('\n'); } else { comments.push(Comment::new( - buf.trim().to_owned(), + buf.trim().replace("\n ", "\n").to_owned(), Span::new( Location { line: start_line, column: start_col }, Location::new(line_num, col), @@ -326,7 +326,6 @@ impl Comments { } line_num += 1; } - Ok(Self { comments }) } @@ -765,4 +764,93 @@ CREATE TABLE posts ( assert_eq!(comment.span().end(), comment_vec[i].span().end()); } } + + #[test] + fn single_line_runover_comments_are_combined() -> Result<(), Box> { + use crate::{ast::ParsedSqlFileSet, comments::Comments, files::SqlFileSet}; + use std::{env, fs}; + + let base = env::temp_dir().join("single_line_runover_combined"); + let _ = fs::remove_dir_all(&base); + fs::create_dir_all(&base)?; + + let file = base.join("runover.sql"); + fs::File::create(&file)?; + + let sql = "\ +-- comment 1 +-- comment 2 +SELECT 1; +"; + fs::write(&file, sql)?; + + let set = SqlFileSet::new(&base, &[])?; + let parsed_set = ParsedSqlFileSet::parse_all(set)?; + + let parsed_file = parsed_set + .files() + .iter() + .find(|f| { + f.file().path().and_then(|p| p.to_str()).is_some_and(|p| p.ends_with("runover.sql")) + }) + .ok_or("runover.sql should be present")?; + + let comments = Comments::parse_all_comments_from_file(parsed_file)?; + let comments = comments.comments(); + + assert_eq!(comments.len(), 1, "expected a single combined comment"); + assert_eq!(comments[0].text(), "comment 1\ncomment 2"); + + assert_eq!(comments[0].span().start(), &Location::new(1, 1)); + assert_eq!(comments[0].span().end().line(), 2); + + let _ = fs::remove_dir_all(&base); + Ok(()) + } + + #[test] + fn single_line_comments_with_gap_are_not_combined() -> Result<(), Box> { + use crate::{ast::ParsedSqlFileSet, comments::Comments, files::SqlFileSet}; + use std::{env, fs}; + + let base = env::temp_dir().join("single_line_runover_not_combined"); + let _ = fs::remove_dir_all(&base); + fs::create_dir_all(&base)?; + + let file = base.join("gap.sql"); + fs::File::create(&file)?; + + let sql = "\ +-- comment 1 + +-- comment 2 +SELECT 1; +"; + fs::write(&file, sql)?; + + let set = SqlFileSet::new(&base, &[])?; + let parsed_set = ParsedSqlFileSet::parse_all(set)?; + + let parsed_file = parsed_set + .files() + .iter() + .find(|f| { + f.file().path().and_then(|p| p.to_str()).is_some_and(|p| p.ends_with("gap.sql")) + }) + .ok_or("gap.sql should be present")?; + + let comments = Comments::parse_all_comments_from_file(parsed_file)?; + let comments = comments.comments(); + + assert_eq!(comments.len(), 2, "expected two separate comments"); + assert_eq!(comments[0].text(), "comment 1"); + assert_eq!(comments[1].text(), "comment 2"); + + assert_eq!(comments[0].span().start(), &Location::new(1, 1)); + assert_eq!(comments[0].span().end().line(), 1); + assert_eq!(comments[1].span().start(), &Location::new(3, 1)); + + let _ = fs::remove_dir_all(&base); + Ok(()) + } } From 4dc3138222443182889e3460eb226b22cced87c4 Mon Sep 17 00:00:00 2001 From: RPG-Alex Date: Fri, 30 Jan 2026 13:09:09 +0800 Subject: [PATCH 11/11] added passing tests for combining sequential singl lines; fixes #20 --- src/comments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/comments.rs b/src/comments.rs index e08db8d..9a7c38a 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -313,7 +313,7 @@ impl Comments { buf.push('\n'); } else { comments.push(Comment::new( - buf.trim().replace("\n ", "\n").to_owned(), + buf.trim().replace("\n ", "\n"), Span::new( Location { line: start_line, column: start_col }, Location::new(line_num, col),