use std::{fmt::Write, ops::Range};
use sway_ast::token::{Comment, CommentKind};
use sway_types::Spanned;
use crate::{
formatter::FormattedCode, utils::map::comments::CommentMap, Formatter, FormatterError,
};
pub type UnformattedCode = String;
#[derive(Debug, Default, Clone)]
pub struct CommentsContext {
pub map: CommentMap,
unformatted_code: UnformattedCode,
}
impl CommentsContext {
pub fn new(map: CommentMap, unformatted_code: UnformattedCode) -> Self {
Self {
map,
unformatted_code,
}
}
pub fn unformatted_code(&self) -> &str {
&self.unformatted_code
}
}
pub fn has_comments<I: Iterator>(comments: I) -> bool {
comments.peekable().peek().is_some()
}
pub fn collect_newlines_after_comment(
comments_context: &CommentsContext,
comment: &Comment,
) -> String {
comments_context.unformatted_code()[comment.span().end()..]
.chars()
.take_while(|&c| c.is_whitespace())
.filter(|&c| c == '\n')
.collect()
}
fn write_trailing_comment(
formatted_code: &mut FormattedCode,
comment: &Comment,
) -> Result<(), FormatterError> {
formatted_code.truncate(formatted_code.trim_end().len());
writeln!(formatted_code, " {}", comment.span().as_str().trim_end())?;
Ok(())
}
pub fn write_comments(
formatted_code: &mut FormattedCode,
range: Range<usize>,
formatter: &mut Formatter,
) -> Result<bool, FormatterError> {
{
let mut comments_iter = formatter
.comments_context
.map
.comments_between(&range)
.peekable();
if comments_iter.peek().is_none() {
return Ok(false);
};
if formatted_code.ends_with(&['{', '}']) && !formatted_code.ends_with('\n') {
writeln!(formatted_code)?;
}
for comment in comments_iter {
let newlines = collect_newlines_after_comment(&formatter.comments_context, comment);
match comment.comment_kind {
CommentKind::Newlined => {
write!(
formatted_code,
"{}{}{}",
formatter.shape.indent.to_string(&formatter.config)?,
comment.span().as_str(),
newlines
)?;
}
CommentKind::Trailing => {
write_trailing_comment(formatted_code, comment)?;
}
}
}
}
formatter
.comments_context
.map
.retain(|bs, _| !bs.contained_within(&range));
Ok(true)
}
#[cfg(test)]
mod tests {
use crate::utils::map::byte_span::ByteSpan;
use std::sync::Arc;
use super::*;
#[test]
fn test_collect_newlines_after_comment() {
let commented_code = r#"contract;
// 10-18
pub fn main() -> bool {
true
}
"#;
let mut comments_ctx = CommentsContext::new(
CommentMap::from_src(Arc::from(commented_code)).unwrap(),
commented_code.to_string(),
);
assert_eq!(
collect_newlines_after_comment(
&comments_ctx,
comments_ctx
.map
.get(&ByteSpan { start: 10, end: 18 })
.unwrap(),
),
"\n"
);
let multiline_comment = r#"contract;
pub fn main() -> bool {
// 38-46
// 51-59
true
}
"#;
comments_ctx = CommentsContext::new(
CommentMap::from_src(Arc::from(multiline_comment)).unwrap(),
multiline_comment.to_string(),
);
assert_eq!(
collect_newlines_after_comment(
&comments_ctx,
comments_ctx
.map
.get(&ByteSpan { start: 38, end: 46 })
.unwrap(),
),
"\n"
);
let multi_newline_comments = r#"contract;
pub fn main() -> bool {
// 38-46
// 52-60
true
}
"#;
comments_ctx = CommentsContext::new(
CommentMap::from_src(Arc::from(multi_newline_comments)).unwrap(),
multi_newline_comments.to_string(),
);
assert_eq!(
collect_newlines_after_comment(
&comments_ctx,
comments_ctx
.map
.get(&ByteSpan { start: 38, end: 46 })
.unwrap(),
),
"\n\n"
);
}
}