use wdl_analysis::Diagnostics;
use wdl_analysis::VisitReason;
use wdl_analysis::Visitor;
use wdl_ast::AstToken;
use wdl_ast::Comment;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use wdl_ast::SyntaxElement;
use wdl_ast::SyntaxKind;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "PreambleCommentPlacement";
fn preamble_comment_outside_preamble(span: Span) -> Diagnostic {
Diagnostic::note("preamble comment after the version statement")
.with_rule(ID)
.with_highlight(span)
.with_fix("do not use `##` comments outside the preamble")
}
#[derive(Default, Debug, Clone, Copy)]
pub struct PreambleCommentPlacementRule {
exited_preamble: bool,
skip_count: usize,
}
impl Rule for PreambleCommentPlacementRule {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Ensures that preamble comments are inside the preamble."
}
fn explanation(&self) -> &'static str {
"Preamble comments should only appear in the preamble section of a WDL document. This rule \
ensures that double-pound comments (`##`) are not used after the version statement."
}
fn tags(&self) -> TagSet {
TagSet::new(&[Tag::Clarity, Tag::Style])
}
fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
None
}
fn related_rules(&self) -> &[&'static str] {
&["PreambleFormatted"]
}
}
impl Visitor for PreambleCommentPlacementRule {
fn reset(&mut self) {
*self = Self::default();
}
fn version_statement(
&mut self,
_diagnostics: &mut Diagnostics,
reason: VisitReason,
_stmt: &wdl_ast::VersionStatement,
) {
if reason == VisitReason::Exit {
self.exited_preamble = true;
}
}
fn comment(&mut self, diagnostics: &mut Diagnostics, comment: &Comment) {
if !self.exited_preamble {
return;
}
if self.skip_count > 0 {
self.skip_count -= 1;
return;
}
if !comment.text().starts_with("## ") {
return;
}
let mut span = comment.span();
let mut current = comment.inner().next_sibling_or_token();
while let Some(sibling) = current {
match sibling.kind() {
SyntaxKind::Comment => {
self.skip_count += 1;
if !sibling
.as_token()
.expect("expected a token")
.text()
.starts_with("## ")
{
break;
}
span = Span::new(
span.start(),
usize::from(sibling.text_range().end()) - span.start(),
)
}
SyntaxKind::Whitespace => {
}
_ => break,
}
current = sibling.next_sibling_or_token();
}
diagnostics.exceptable_add(
preamble_comment_outside_preamble(span),
SyntaxElement::from(comment.inner().clone()),
&self.exceptable_nodes(),
);
}
}