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::Expr;
use wdl_ast::v1::LiteralExpr;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "RedundantNone";
fn redundant_none(span: Span, name: &str) -> Diagnostic {
Diagnostic::note(format!(
"redundant assignment of `None` to optional input `{name}`"
))
.with_rule(ID)
.with_highlight(span)
.with_fix(format!("remove `= None` for input `{name}`"))
}
#[derive(Debug, Default, Clone)]
pub struct RedundantNone;
impl Rule for RedundantNone {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Flags redundant assignment of `None` to optional inputs."
}
fn explanation(&self) -> &'static str {
"The specification states that an optional input declaration (e.g., `String? foo`) is \
implicitly initialized to `None` if no default is provided. Therefore explicitly writing \
`String? foo = None` is equivalent to `String? foo` but adds unnecessary verbosity."
}
fn examples(&self) -> &'static [Example] {
&[Example {
negative: LabeledSnippet {
label: None,
snippet: r#"version 1.2
workflow example {
input {
String? name = None
}
}
"#,
},
revised: Some(LabeledSnippet {
label: None,
snippet: r#"version 1.2
workflow example {
input {
String? name
}
}
"#,
}),
}]
}
fn tags(&self) -> crate::TagSet {
TagSet::new(&[Tag::Style])
}
fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
Some(&[
SyntaxKind::VersionStatementNode,
SyntaxKind::InputSectionNode,
SyntaxKind::BoundDeclNode,
])
}
fn related_rules(&self) -> &'static [&'static str] {
&[]
}
}
impl Visitor for RedundantNone {
fn reset(&mut self) {
*self = Default::default();
}
fn bound_decl(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
decl: &wdl_ast::v1::BoundDecl,
) {
if reason == VisitReason::Exit {
return;
}
if decl
.inner()
.parent()
.is_none_or(|p| p.kind() != SyntaxKind::InputSectionNode)
{
return;
}
if !decl.ty().is_optional() {
return;
}
let expr = decl.expr();
if matches!(expr, Expr::Literal(LiteralExpr::None(_))) {
let diagnostic = redundant_none(expr.span(), decl.name().text());
diagnostics.exceptable_add(diagnostic, decl.inner(), &self.exceptable_nodes());
}
}
}