| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Provides a representation of source code comments in parsed SQL code. |
| //! |
| //! See [Comments::find] for an example. |
| |
| #[cfg(not(feature = "std"))] |
| use alloc::{string::String, vec::Vec}; |
| |
| use core::{ |
| ops::{Bound, Deref, RangeBounds}, |
| slice, |
| }; |
| |
| use crate::tokenizer::{Location, Span}; |
| |
| /// An opaque container for comments from a parse SQL source code. |
| #[derive(Default, Debug, Clone)] |
| pub struct Comments(Vec<CommentWithSpan>); |
| |
| impl Comments { |
| /// Accepts `comment` if its the first or is located strictly after the |
| /// last accepted comment. In other words, this method will skip the |
| /// comment if its comming out of order (as encountered in the parsed |
| /// source code.) |
| pub(crate) fn offer(&mut self, comment: CommentWithSpan) { |
| if self |
| .0 |
| .last() |
| .map(|last| last.span < comment.span) |
| .unwrap_or(true) |
| { |
| self.0.push(comment); |
| } |
| } |
| |
| /// Finds comments starting within the given location range. The order of |
| /// iterator reflects the order of the comments as encountered in the parsed |
| /// source code. |
| /// |
| /// # Example |
| /// ```rust |
| /// use sqlparser::{dialect::GenericDialect, parser::Parser, tokenizer::Location}; |
| /// |
| /// let sql = r#"/* |
| /// header comment ... |
| /// ... spanning multiple lines |
| /// */ |
| /// |
| /// -- first statement |
| /// SELECT 'hello' /* world */ FROM DUAL; |
| /// |
| /// -- second statement |
| /// SELECT 123 FROM DUAL; |
| /// |
| /// -- trailing comment |
| /// "#; |
| /// |
| /// let (ast, comments) = Parser::parse_sql_with_comments(&GenericDialect, sql).unwrap(); |
| /// |
| /// // all comments appearing before line seven, i.e. before the first statement itself |
| /// assert_eq!( |
| /// &comments.find(..Location::new(7, 1)).map(|c| c.as_str()).collect::<Vec<_>>(), |
| /// &["\n header comment ...\n ... spanning multiple lines\n", " first statement\n"]); |
| /// |
| /// // all comments appearing within the first statement |
| /// assert_eq!( |
| /// &comments.find(Location::new(7, 1)..Location::new(8,1)).map(|c| c.as_str()).collect::<Vec<_>>(), |
| /// &[" world "]); |
| /// |
| /// // all comments appearing within or after the first statement |
| /// assert_eq!( |
| /// &comments.find(Location::new(7, 1)..).map(|c| c.as_str()).collect::<Vec<_>>(), |
| /// &[" world ", " second statement\n", " trailing comment\n"]); |
| /// ``` |
| /// |
| /// The [Spanned](crate::ast::Spanned) trait allows you to access location |
| /// information for certain AST nodes. |
| pub fn find<R: RangeBounds<Location>>(&self, range: R) -> Iter<'_> { |
| let (start, end) = ( |
| self.start_index(range.start_bound()), |
| self.end_index(range.end_bound()), |
| ); |
| debug_assert!((0..=self.0.len()).contains(&start)); |
| debug_assert!((0..=self.0.len()).contains(&end)); |
| // in case the user specified a reverse range |
| Iter(if start <= end { |
| self.0[start..end].iter() |
| } else { |
| self.0[0..0].iter() |
| }) |
| } |
| |
| /// Find the index of the first comment starting "before" the given location. |
| /// |
| /// The returned index is _inclusive_ and within the range of `0..=self.0.len()`. |
| fn start_index(&self, location: Bound<&Location>) -> usize { |
| match location { |
| Bound::Included(location) => { |
| match self.0.binary_search_by(|c| c.span.start.cmp(location)) { |
| Ok(i) => i, |
| Err(i) => i, |
| } |
| } |
| Bound::Excluded(location) => { |
| match self.0.binary_search_by(|c| c.span.start.cmp(location)) { |
| Ok(i) => i + 1, |
| Err(i) => i, |
| } |
| } |
| Bound::Unbounded => 0, |
| } |
| } |
| |
| /// Find the index of the first comment starting "after" the given location. |
| /// |
| /// The returned index is _exclusive_ and within the range of `0..=self.0.len()`. |
| fn end_index(&self, location: Bound<&Location>) -> usize { |
| match location { |
| Bound::Included(location) => { |
| match self.0.binary_search_by(|c| c.span.start.cmp(location)) { |
| Ok(i) => i + 1, |
| Err(i) => i, |
| } |
| } |
| Bound::Excluded(location) => { |
| match self.0.binary_search_by(|c| c.span.start.cmp(location)) { |
| Ok(i) => i, |
| Err(i) => i, |
| } |
| } |
| Bound::Unbounded => self.0.len(), |
| } |
| } |
| } |
| |
| impl From<Comments> for Vec<CommentWithSpan> { |
| fn from(comments: Comments) -> Self { |
| comments.0 |
| } |
| } |
| |
| /// A source code comment with information of its entire span. |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct CommentWithSpan { |
| /// The source code comment iself |
| pub comment: Comment, |
| /// The span of the comment including its markers |
| pub span: Span, |
| } |
| |
| impl Deref for CommentWithSpan { |
| type Target = Comment; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.comment |
| } |
| } |
| |
| /// A unified type of the different source code comment formats. |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum Comment { |
| /// A single line comment, typically introduced with a prefix and spanning |
| /// until end-of-line or end-of-file in the source code. |
| /// |
| /// Note: `content` will include the terminating new-line character, if any. |
| /// A single-line comment, typically introduced with a prefix and spanning |
| /// until end-of-line or end-of-file in the source code. |
| /// |
| /// Note: `content` will include the terminating new-line character, if any. |
| SingleLine { |
| /// The content of the comment (including trailing newline, if any). |
| content: String, |
| /// The prefix introducing the comment (e.g. `--`, `#`). |
| prefix: String, |
| }, |
| |
| /// A multi-line comment, typically enclosed in `/* .. */` markers. The |
| /// string represents the content excluding the markers. |
| MultiLine(String), |
| } |
| |
| impl Comment { |
| /// Retrieves the content of the comment as string slice. |
| pub fn as_str(&self) -> &str { |
| match self { |
| Comment::SingleLine { content, prefix: _ } => content.as_str(), |
| Comment::MultiLine(content) => content.as_str(), |
| } |
| } |
| } |
| |
| impl Deref for Comment { |
| type Target = str; |
| |
| fn deref(&self) -> &Self::Target { |
| self.as_str() |
| } |
| } |
| |
| /// An opaque iterator implementation over comments served by [Comments::find]. |
| pub struct Iter<'a>(slice::Iter<'a, CommentWithSpan>); |
| |
| impl<'a> Iterator for Iter<'a> { |
| type Item = &'a CommentWithSpan; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| self.0.next() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_find() { |
| let comments = { |
| // ``` |
| // -- abc |
| // /* hello */--, world |
| // /* def |
| // ghi |
| // jkl |
| // */ |
| // ``` |
| let mut c = Comments(Vec::new()); |
| c.offer(CommentWithSpan { |
| comment: Comment::SingleLine { |
| content: " abc".into(), |
| prefix: "--".into(), |
| }, |
| span: Span::new((1, 1).into(), (1, 7).into()), |
| }); |
| c.offer(CommentWithSpan { |
| comment: Comment::MultiLine(" hello ".into()), |
| span: Span::new((2, 3).into(), (2, 14).into()), |
| }); |
| c.offer(CommentWithSpan { |
| comment: Comment::SingleLine { |
| content: ", world".into(), |
| prefix: "--".into(), |
| }, |
| span: Span::new((2, 14).into(), (2, 21).into()), |
| }); |
| c.offer(CommentWithSpan { |
| comment: Comment::MultiLine(" def\n ghi\n jkl\n".into()), |
| span: Span::new((3, 3).into(), (7, 1).into()), |
| }); |
| c |
| }; |
| |
| fn find<R: RangeBounds<Location>>(comments: &Comments, range: R) -> Vec<&str> { |
| comments.find(range).map(|c| c.as_str()).collect::<Vec<_>>() |
| } |
| |
| // ~ end-points only -------------------------------------------------- |
| assert_eq!(find(&comments, ..Location::new(0, 0)), Vec::<&str>::new()); |
| assert_eq!(find(&comments, ..Location::new(2, 1)), vec![" abc"]); |
| assert_eq!(find(&comments, ..Location::new(2, 3)), vec![" abc"]); |
| assert_eq!( |
| find(&comments, ..=Location::new(2, 3)), |
| vec![" abc", " hello "] |
| ); |
| assert_eq!( |
| find(&comments, ..=Location::new(2, 3)), |
| vec![" abc", " hello "] |
| ); |
| assert_eq!( |
| find(&comments, ..Location::new(2, 15)), |
| vec![" abc", " hello ", ", world"] |
| ); |
| |
| // ~ start-points only ------------------------------------------------ |
| assert_eq!( |
| find(&comments, Location::new(1000, 1000)..), |
| Vec::<&str>::new() |
| ); |
| assert_eq!( |
| find(&comments, Location::new(2, 14)..), |
| vec![", world", " def\n ghi\n jkl\n"] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(2, 15)..), |
| vec![" def\n ghi\n jkl\n"] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(0, 0)..), |
| vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..), |
| vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"] |
| ); |
| |
| // ~ ranges ----------------------------------------------------------- |
| assert_eq!( |
| find(&comments, Location::new(2, 1)..Location::new(1, 1)), |
| Vec::<&str>::new() |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..Location::new(2, 3)), |
| vec![" abc"] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..=Location::new(2, 3)), |
| vec![" abc", " hello "] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..=Location::new(2, 10)), |
| vec![" abc", " hello "] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..=Location::new(2, 14)), |
| vec![" abc", " hello ", ", world"] |
| ); |
| assert_eq!( |
| find(&comments, Location::new(1, 1)..Location::new(2, 15)), |
| vec![" abc", " hello ", ", world"] |
| ); |
| |
| // ~ find everything -------------------------------------------------- |
| assert_eq!( |
| find(&comments, ..), |
| vec![" abc", " hello ", ", world", " def\n ghi\n jkl\n"] |
| ); |
| } |
| } |