use wdl_analysis::Diagnostics;
use wdl_analysis::Document;
use wdl_analysis::Example;
use wdl_analysis::LabeledSnippet;
use wdl_analysis::VisitReason;
use wdl_analysis::Visitor;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::SyntaxKind;
use wdl_ast::v1::MetadataSection;
use wdl_ast::v1::SectionParent;
use wdl_ast::version::V1;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "MetaDescription";
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.text()
))
.with_rule(ID)
.with_highlight(span)
.with_fix("add a `description` key to the meta section")
}
#[derive(Default, Debug, Clone, Copy)]
pub struct MetaDescriptionRule {
version: Option<SupportedVersion>,
in_struct: bool,
}
impl Rule for MetaDescriptionRule {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Ensures the `meta` section contains a `description` key."
}
fn explanation(&self) -> &'static str {
"Each task, workflow, and struct should have a description in the meta section. The \
description should be short, written in active voice, and be in complete sentences. More \
detailed information can be included in the `help` key."
}
fn examples(&self) -> &'static [Example] {
&[Example {
negative: LabeledSnippet {
label: None,
snippet: r#"version 1.2
task say_hello {
meta {
}
input {
String name
}
command <<<
echo "Hello, ~{name}!"
>>>
}
"#,
},
revised: Some(LabeledSnippet {
label: None,
snippet: r#"version 1.2
task say_hello {
meta {
description: "Says hello for the given name"
}
input {
String name
}
command <<<
echo "Hello, ~{name}!"
>>>
}
"#,
}),
}]
}
fn tags(&self) -> TagSet {
TagSet::new(&[
Tag::Completeness,
Tag::Documentation,
Tag::SprocketCompatibility,
])
}
fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
Some(&[
SyntaxKind::VersionStatementNode,
SyntaxKind::MetadataSectionNode,
])
}
fn related_rules(&self) -> &'static [&'static str] {
&[
"ParameterMetaMatched",
"ParameterDescription",
"OutputSection",
"RequirementsSection",
"RuntimeSection",
"MatchingOutputMeta",
"DescriptionLength",
]
}
}
impl Visitor for MetaDescriptionRule {
fn reset(&mut self) {
*self = Default::default();
}
fn document(
&mut self,
_: &mut Diagnostics,
reason: VisitReason,
_: &Document,
version: SupportedVersion,
) {
if reason == VisitReason::Exit {
return;
}
self.version = Some(version);
}
fn struct_definition(
&mut self,
_: &mut Diagnostics,
reason: VisitReason,
_: &wdl_ast::v1::StructDefinition,
) {
self.in_struct = reason == VisitReason::Enter;
}
fn metadata_section(
&mut self,
diagnostics: &mut Diagnostics,
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().inner().to_string() == "description");
if description.is_none() {
diagnostics.exceptable_add(
description_missing(
section
.inner()
.first_token()
.expect("metadata section should have tokens")
.text_range()
.into(),
section.parent(),
),
section.inner(),
&self.exceptable_nodes(),
);
}
}
}