use rowan::ast::support;
use wdl_analysis::Diagnostics;
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::SyntaxKind;
use wdl_ast::v1::CommandSection;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "HereDocCommands";
fn curly_commands(task: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!(
"task `{task}` uses curly braces in command section"
))
.with_rule(ID)
.with_label("this command section uses curly braces", span)
.with_fix("instead of curly braces, use heredoc syntax (<<<>>>>) for command sections")
}
#[derive(Default, Debug, Clone, Copy)]
pub struct HereDocCommandsRule;
impl Rule for HereDocCommandsRule {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Ensures that tasks use heredoc syntax in command sections."
}
fn explanation(&self) -> &'static str {
"Curly command blocks are no longer considered idiomatic WDL. Idiomatic WDL code uses \
heredoc command blocks instead. This is because curly command blocks create ambiguity \
with Bash syntax."
}
fn examples(&self) -> &'static [Example] {
&[Example {
negative: LabeledSnippet {
label: None,
snippet: r#"version 1.2
task say_hello {
command {
echo "Hello, World!"
}
}
"#,
},
revised: Some(LabeledSnippet {
label: None,
snippet: r#"version 1.2
task say_hello {
command <<<
echo "Hello, World!"
>>>
}
"#,
}),
}]
}
fn tags(&self) -> TagSet {
TagSet::new(&[Tag::Clarity, Tag::Correctness])
}
fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
Some(&[
SyntaxKind::VersionStatementNode,
SyntaxKind::CommandSectionNode,
])
}
fn related_rules(&self) -> &'static [&'static str] {
&[]
}
}
impl Visitor for HereDocCommandsRule {
fn reset(&mut self) {
*self = Self;
}
fn command_section(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
section: &CommandSection,
) {
if reason == VisitReason::Exit {
return;
}
if !section.is_heredoc() {
let name = section.parent().name();
let command_keyword = support::token(section.inner(), SyntaxKind::CommandKeyword)
.expect("should have a command keyword token");
diagnostics.exceptable_add(
curly_commands(name.text(), command_keyword.text_range().into()),
section.inner(),
&self.exceptable_nodes(),
);
}
}
}