use wdl_ast::v1::MetadataSection;
use wdl_ast::v1::SectionParent;
use wdl_ast::version::V1;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Diagnostics;
use wdl_ast::Document;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::SyntaxElement;
use wdl_ast::SyntaxKind;
use wdl_ast::ToSpan;
use wdl_ast::VisitReason;
use wdl_ast::Visitor;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "DescriptionMissing";
fn description_missing(span: Span, parent: SectionParent) -> Diagnostic {
let (ty, name) = match parent {
SectionParent::Task(t) => ("task", t.name()),
SectionParent::Workflow(w) => ("workflow", w.name()),
SectionParent::Struct(s) => ("struct", s.name()),
};
Diagnostic::note(format!(
"{ty} `{name}` is missing a description key",
name = name.as_str()
))
.with_rule(ID)
.with_highlight(span)
.with_fix("add a `description` key to this meta section")
}
#[derive(Default, Debug, Clone, Copy)]
pub struct DescriptionMissingRule {
version: Option<SupportedVersion>,
in_struct: bool,
}
impl Rule for DescriptionMissingRule {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Ensures that a description is present for each meta section."
}
fn explanation(&self) -> &'static str {
"Each task or workflow should have a description in the meta section. This description \
should be an explanation of the task or workflow. The description should be written in \
active voice and complete sentences. More detailed information can be included in the \
`help` key."
}
fn tags(&self) -> TagSet {
TagSet::new(&[Tag::Completeness])
}
fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
Some(&[
SyntaxKind::VersionStatementNode,
SyntaxKind::MetadataSectionNode,
])
}
}
impl Visitor for DescriptionMissingRule {
type State = Diagnostics;
fn document(
&mut self,
_: &mut Self::State,
reason: VisitReason,
_: &Document,
version: SupportedVersion,
) {
if reason == VisitReason::Exit {
return;
}
*self = Default::default();
self.version = Some(version);
}
fn struct_definition(
&mut self,
_: &mut Self::State,
reason: VisitReason,
_: &wdl_ast::v1::StructDefinition,
) {
self.in_struct = reason == VisitReason::Enter;
}
fn metadata_section(
&mut self,
state: &mut Self::State,
reason: VisitReason,
section: &MetadataSection,
) {
if reason == VisitReason::Exit {
return;
}
if self.in_struct
&& self.version.expect("should have version") < SupportedVersion::V1(V1::Two)
{
return;
}
let description = section
.items()
.find(|entry| entry.name().syntax().to_string() == "description");
if description.is_none() {
state.exceptable_add(
description_missing(
section
.syntax()
.first_token()
.unwrap()
.text_range()
.to_span(),
section.parent(),
),
SyntaxElement::from(section.syntax().clone()),
&self.exceptable_nodes(),
);
}
}
}