Skip to content

feat(fmt): wrap collect comments #3786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 109 additions & 9 deletions fmt/src/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use itertools::Itertools;
use solang_parser::pt::*;
use std::collections::VecDeque;
use std::{collections::VecDeque, slice::Iter};

/// The type of a Comment
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -35,7 +35,7 @@ pub struct CommentWithMetadata {
pub loc: Loc,
pub has_newline_before: bool,
pub indent_len: usize,
pub comment: String,
pub raw_contents: Vec<String>,
pub position: CommentPosition,
}

Expand Down Expand Up @@ -65,7 +65,7 @@ impl CommentWithMetadata {
Comment::DocBlock(loc, comment) => (CommentType::DocBlock, loc, comment),
};
Self {
comment: comment.trim_end().to_string(),
raw_contents: vec![comment.trim_end().to_string()],
ty,
loc,
position,
Expand All @@ -74,6 +74,35 @@ impl CommentWithMetadata {
}
}

/// Return a flag indicating whether this comment can be extended
/// with the contents of another comment
pub fn can_be_extended(&self, other: &CommentWithMetadata) -> bool {
let other_first_ch = other.contents().next().and_then(|c| c.trim_start().chars().next());
self.is_line() &&
other.is_line() &&
self.position == other.position &&
self.loc.start() < other.loc.end() &&
!other.has_newline_before &&
other_first_ch.map(|ch| ch.is_alphanumeric()).unwrap_or_default()
}

/// Extends the comment with another comment contents
/// and metadata. Does not perform any additional validation
pub fn extend(&self, other: &CommentWithMetadata) -> Self {
let mut comments = self.raw_contents.clone();
comments.extend(other.raw_contents.iter().cloned());
let mut loc = self.loc;
loc.use_end_from(&other.loc);
Self {
raw_contents: comments,
ty: self.ty,
loc,
position: self.position,
has_newline_before: self.has_newline_before,
indent_len: self.indent_len,
}
}

/// Construct a comment with metadata by analyzing its surrounding source code
fn from_comment_and_src(
comment: Comment,
Expand Down Expand Up @@ -174,11 +203,28 @@ impl CommentWithMetadata {
self.loc.start() < byte
}

pub fn contents(&self) -> &str {
self.comment
.strip_prefix(self.start_token())
.map(|c| self.end_token().and_then(|end| c.strip_suffix(end)).unwrap_or(c))
.unwrap_or(&self.comment)
pub fn contents(&self) -> CommentContents<'_, Iter<'_, String>> {
CommentContents::new(self.raw_contents.iter(), self.start_token(), self.end_token())
}

/// Return the whitespace padding for the lines following the first
pub fn padding(&self) -> Option<usize> {
if self.is_line() {
self.contents()
.next()
.and_then(|content| content.lines().next())
.and_then(|line| line.trim_start().chars().next().map(|ch| (line, ch)))
.and_then(|(line, ch)| {
// TODO: 1. and 1)
if ch == '-' || ch == '*' {
line.trim_start().find(char::is_alphanumeric)
} else {
None
}
})
} else {
None
}
}

/// The start token of the comment
Expand Down Expand Up @@ -211,6 +257,57 @@ impl CommentWithMetadata {
}
}

/// Iterator over comment contents
pub struct CommentContents<'a, I>
where
I: Iterator<Item = &'a String>,
{
contents: I,
start_token: &'a str,
end_token: Option<&'a str>,
is_first: bool,
peeked: Option<Option<&'a String>>,
}

impl<'a, I> CommentContents<'a, I>
where
I: Iterator<Item = &'a String>,
{
fn new(contents: I, start_token: &'a str, end_token: Option<&'a str>) -> Self {
Self { contents, start_token, end_token, is_first: true, peeked: None }
}

pub fn peek_next_word(&mut self) -> Option<&'a str> {
let iter = &mut self.contents;
self.peeked
.get_or_insert_with(|| iter.next())
.and_then(|content| content.lines().next())
.and_then(|line| {
line.strip_prefix(self.start_token).unwrap_or(line).trim_start().split(' ').next()
})
}
}

impl<'a, I: Iterator<Item = &'a String>> Iterator for CommentContents<'a, I> {
type Item = &'a str;

fn next(&mut self) -> Option<Self::Item> {
self.peeked.take().flatten().or_else(|| self.contents.next()).map(|content| {
let mut comment = content.as_str();
comment = comment.strip_prefix(self.start_token).unwrap_or(comment);
if let Some(end_token) = self.end_token {
comment = comment.strip_suffix(end_token).unwrap_or(comment);
}
if !self.is_first {
comment = comment.trim_start();
} else {
self.is_first = false;
}
comment
})
}
}

/// A list of comments
#[derive(Debug, Clone)]
pub struct Comments {
Expand Down Expand Up @@ -293,7 +390,10 @@ impl Comments {
{
self.iter()
.filter_map(|comment| {
Some((comment, comment.contents().trim_start().strip_prefix("forgefmt:")?.trim()))
Some((
comment,
comment.contents().next()?.trim_start().strip_prefix("forgefmt:")?.trim(),
))
})
.map(|(comment, item)| {
let loc = comment.loc;
Expand Down
Loading