use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use wdl_ast::SupportedVersion;
use wdl_ast::v1;
use wdl_ast::v1::Exponentiation;
use wdl_ast::v1::Expr;
use wdl_ast::v1::HintsKeyword;
use wdl_ast::v1::InputKeyword;
use wdl_ast::v1::MetaKeyword;
use wdl_ast::v1::ParameterMetaKeyword;
use wdl_ast::v1::RequirementsKeyword;
use wdl_ast::version::V1;
use crate::Config;
use crate::Diagnostics;
use crate::VisitReason;
use crate::Visitor;
use crate::document::Document;
fn exponentiation_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of the exponentiation operator requires WDL version 1.2")
.with_highlight(span)
}
fn requirements_section(span: Span) -> Diagnostic {
Diagnostic::error("use of the `requirements` section requires WDL version 1.2")
.with_highlight(span)
}
fn hints_section(span: Span) -> Diagnostic {
Diagnostic::error("use of the `hints` section requires WDL version 1.2").with_highlight(span)
}
fn multiline_string_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of multi-line strings requires WDL version 1.2").with_highlight(span)
}
fn directory_type_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of the `Directory` type requires WDL version 1.2").with_highlight(span)
}
fn implicit_binding_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of implicit input bindings requires WDL version 1.1 or later")
.with_highlight(span)
}
fn input_keyword_requirement(span: Span) -> Diagnostic {
Diagnostic::error("omitting the `input` keyword in a call statement requires WDL version 1.2")
.with_label("missing an `input` keyword before this input", span)
.with_fix("add an `input` keyword followed by a colon before any call inputs")
}
fn struct_literal_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of a struct literal requires WDL version 1.1 or later")
.with_highlight(span)
}
fn struct_metadata_requirement(kind: &str, span: Span) -> Diagnostic {
Diagnostic::error(format!(
"use of a `{kind}` section in a struct definition requires WDL version 1.2"
))
.with_highlight(span)
}
fn env_var_requirement(span: Span) -> Diagnostic {
Diagnostic::error("use of environment variable declarations requires WDL version 1.2")
.with_highlight(span)
}
fn deprecated_version_feature_flag(flag_name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!(
"the `{flag_name}` feature flag is deprecated; please remove this feature flag from your \
configuration file"
))
.with_highlight(span)
}
fn unsupported_version(version: SupportedVersion, span: Span) -> Diagnostic {
Diagnostic::error(format!("unsupported version {version}")).with_highlight(span)
}
#[derive(Clone, Copy, Debug, Default)]
struct DeprecatedVersionFeatureFlag {
explicitly_disabled: bool,
warning_emitted: bool,
}
#[derive(Debug, Default)]
pub struct VersionVisitor {
wdl_1_3_ff: DeprecatedVersionFeatureFlag,
version: Option<SupportedVersion>,
}
impl Visitor for VersionVisitor {
fn register(&mut self, config: &Config) {
self.wdl_1_3_ff.explicitly_disabled = !config.feature_flags().wdl_1_3();
}
fn reset(&mut self) {
let wdl_1_3_ff = self.wdl_1_3_ff;
*self = Default::default();
self.wdl_1_3_ff = wdl_1_3_ff;
}
fn document(
&mut self,
_: &mut Diagnostics,
reason: VisitReason,
_: &Document,
version: SupportedVersion,
) {
if reason == VisitReason::Exit {
return;
}
self.version = Some(version);
}
fn version_statement(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
stmt: &wdl_ast::VersionStatement,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version {
match version {
SupportedVersion::V1(V1::Three)
if self.wdl_1_3_ff.explicitly_disabled && !self.wdl_1_3_ff.warning_emitted =>
{
diagnostics.add(deprecated_version_feature_flag(
"wdl_1_3",
stmt.version().span(),
));
self.wdl_1_3_ff.warning_emitted = true;
}
SupportedVersion::V1(V1::Zero | V1::One | V1::Two | V1::Three) => {}
other => diagnostics.add(unsupported_version(other, stmt.version().span())),
}
}
}
fn requirements_section(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
section: &v1::RequirementsSection,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version
&& version < SupportedVersion::V1(V1::Two)
{
diagnostics.add(requirements_section(
section
.token::<RequirementsKeyword<_>>()
.expect("should have keyword")
.span(),
));
}
}
fn task_hints_section(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
section: &v1::TaskHintsSection,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version
&& version < SupportedVersion::V1(V1::Two)
{
diagnostics.add(hints_section(
section
.token::<HintsKeyword<_>>()
.expect("should have keyword")
.span(),
));
}
}
fn workflow_hints_section(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
section: &v1::WorkflowHintsSection,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version
&& version < SupportedVersion::V1(V1::Two)
{
diagnostics.add(hints_section(
section
.token::<HintsKeyword<_>>()
.expect("should have keyword")
.span(),
));
}
}
fn expr(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, expr: &v1::Expr) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version {
match expr {
Expr::Exponentiation(e) if version < SupportedVersion::V1(V1::Two) => {
diagnostics.add(exponentiation_requirement(
e.token::<Exponentiation<_>>()
.expect("should have operator")
.span(),
));
}
v1::Expr::Literal(v1::LiteralExpr::String(s))
if version < SupportedVersion::V1(V1::Two)
&& s.kind() == v1::LiteralStringKind::Multiline =>
{
diagnostics.add(multiline_string_requirement(s.span()));
}
_ => {}
}
}
}
fn bound_decl(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
decl: &v1::BoundDecl,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version {
if let Some(env) = decl.env()
&& version < SupportedVersion::V1(V1::Two)
{
diagnostics.add(env_var_requirement(env.span()));
}
if let v1::Type::Primitive(ty) = decl.ty()
&& version < SupportedVersion::V1(V1::Two)
&& ty.kind() == v1::PrimitiveTypeKind::Directory
{
diagnostics.add(directory_type_requirement(ty.span()));
}
if version < SupportedVersion::V1(V1::One)
&& let expr @ v1::Expr::Literal(v1::LiteralExpr::Struct(_)) = decl.expr()
{
diagnostics.add(struct_literal_requirement(expr.span()));
}
}
}
fn unbound_decl(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
decl: &v1::UnboundDecl,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version {
if let Some(env) = decl.env()
&& version < SupportedVersion::V1(V1::Two)
{
diagnostics.add(env_var_requirement(env.span()));
}
if let v1::Type::Primitive(ty) = decl.ty()
&& version < SupportedVersion::V1(V1::Two)
&& ty.kind() == v1::PrimitiveTypeKind::Directory
{
diagnostics.add(directory_type_requirement(ty.span()));
}
}
}
fn call_statement(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
stmt: &v1::CallStatement,
) {
if reason == VisitReason::Exit {
return;
}
let Some(version) = self.version else {
return;
};
if version < SupportedVersion::V1(V1::Two) {
if let Some(input) = stmt.inputs().next()
&& stmt.token::<InputKeyword<_>>().is_none()
{
diagnostics.add(input_keyword_requirement(input.span()));
}
}
if version < SupportedVersion::V1(V1::One) {
for input in stmt.inputs() {
if input.is_implicit_bind() {
diagnostics.add(implicit_binding_requirement(input.span()));
}
}
}
}
fn struct_definition(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
def: &v1::StructDefinition,
) {
if reason == VisitReason::Exit {
return;
}
if let Some(version) = self.version
&& version < SupportedVersion::V1(V1::Two)
{
if let Some(section) = def.metadata().next() {
diagnostics.add(struct_metadata_requirement(
"meta",
section
.token::<MetaKeyword<_>>()
.expect("should have keyword")
.span(),
));
}
if let Some(section) = def.parameter_metadata().next() {
diagnostics.add(struct_metadata_requirement(
"parameter_meta",
section
.token::<ParameterMetaKeyword<_>>()
.expect("should have keyword")
.span(),
));
}
}
}
}