use crate::comment::Comment;
use crate::declaration::Declaration;
use crate::import::Import;
use crate::module_header::ModuleHeader;
use crate::node::Spanned;
use crate::token::Token;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ElmModule {
pub header: Spanned<ModuleHeader>,
pub imports: Vec<Spanned<Import>>,
pub declarations: Vec<Spanned<Declaration>>,
pub comments: Vec<Spanned<Comment>>,
}
impl ElmModule {
pub fn leading_comments(&self, decl_index: usize) -> Vec<&Spanned<Comment>> {
if decl_index >= self.declarations.len() {
return Vec::new();
}
let decl_start = self.declarations[decl_index].span.start.offset;
let prev_end = if decl_index > 0 {
self.declarations[decl_index - 1].span.end.offset
} else if let Some(last_import) = self.imports.last() {
last_import.span.end.offset
} else {
self.header.span.end.offset
};
self.comments
.iter()
.filter(|c| c.span.start.offset > prev_end && c.span.end.offset <= decl_start)
.collect()
}
pub fn trailing_comment(&self, decl_index: usize) -> Option<&Spanned<Comment>> {
if decl_index >= self.declarations.len() {
return None;
}
let decl_end_line = self.declarations[decl_index].span.end.line;
let next_start = if decl_index + 1 < self.declarations.len() {
self.declarations[decl_index + 1].span.start.offset
} else {
usize::MAX
};
self.comments.iter().find(|c| {
c.span.start.line == decl_end_line
&& c.span.start.offset < next_start
&& matches!(c.value, Comment::Line(_))
})
}
pub fn module_comments(&self) -> Vec<&Spanned<Comment>> {
let first_decl_start = self
.declarations
.first()
.map(|d| d.span.start.offset)
.unwrap_or(usize::MAX);
self.comments
.iter()
.filter(|c| c.span.end.offset <= first_decl_start)
.collect()
}
}
pub fn extract_comments(tokens: &[Spanned<Token>]) -> Vec<Spanned<Comment>> {
tokens
.iter()
.filter_map(|tok| match &tok.value {
Token::LineComment(text) => Some(Spanned::new(tok.span, Comment::Line(text.clone()))),
Token::BlockComment(text) => Some(Spanned::new(tok.span, Comment::Block(text.clone()))),
Token::DocComment(text) => Some(Spanned::new(tok.span, Comment::Doc(text.clone()))),
_ => None,
})
.collect()
}
pub fn associate_comments(
module: &ElmModule,
all_comments: &[Spanned<Comment>],
) -> Vec<Vec<Spanned<Comment>>> {
let mut result = Vec::with_capacity(module.declarations.len());
for (i, decl) in module.declarations.iter().enumerate() {
let decl_start_line = decl.span.start.line;
let prev_start_line = if i > 0 {
module.declarations[i - 1].span.start.line
} else if let Some(last_import) = module.imports.last() {
last_import.span.end.line
} else {
module.header.span.end.line
};
let leading: Vec<Spanned<Comment>> = all_comments
.iter()
.filter(|c| c.span.start.line > prev_start_line && c.span.start.line < decl_start_line)
.cloned()
.collect();
result.push(leading);
}
result
}