use super::super::VersioningError;
use super::super::timeline::VersionTimeline;
use super::availability::{Availability, Site, VersionVisitor, availability, raw_versioning_name};
use crate::error::ParserResult;
use crate::hir::{Annotation, Definition, Enumerator, VersionParameter};
pub(crate) struct Discovery {
pub(crate) version_enum: Option<(String, Vec<Enumerator>)>,
pub(crate) saw_lifecycle: Option<(String, String)>,
pub(crate) version_parameter: Option<VersionParameter>,
}
struct DiscoverVisitor {
discovery: Discovery,
}
impl VersionVisitor for DiscoverVisitor {
fn enter(&mut self, site: &Site<'_>, _parent: Availability) -> ParserResult<Availability> {
site.validate_supported_annotations()?;
let mut marker = false;
let mut lifecycle: Vec<String> = Vec::new();
let mut parameter: Option<VersionParameter> = None;
for annotation in site.annotations {
match annotation {
Annotation::Version => {
if marker {
return Err(VersioningError::DuplicateVersionParameter {
decl: site.path.clone(),
}
.into());
}
marker = true;
}
Annotation::VersionParameter(value) => {
if marker {
return Err(VersioningError::DuplicateVersionParameter {
decl: site.path.clone(),
}
.into());
}
marker = true;
value.validate().map_err(|reason| {
VersioningError::MalformedVersionParameter {
decl: site.path.clone(),
reason,
}
})?;
parameter = Some(value.clone());
}
Annotation::Added { .. } => lifecycle.push("@added".to_string()),
Annotation::Removed { .. } => lifecycle.push("@removed".to_string()),
other => {
if raw_versioning_name(other).is_some_and(|name| {
crate::lint::AnnotationKind::from_name(&name)
== Some(crate::lint::AnnotationKind::Version)
}) {
if marker {
return Err(VersioningError::DuplicateVersionParameter {
decl: site.path.clone(),
}
.into());
}
marker = true;
let value = crate::hir::annotation_params(other).ok_or_else(|| {
VersioningError::MalformedVersionParameter {
decl: site.path.clone(),
reason: "requires in=... and name=...".to_string(),
}
})?;
parameter = Some(VersionParameter::parse(value).map_err(|reason| {
VersioningError::MalformedVersionParameter {
decl: site.path.clone(),
reason,
}
})?);
}
}
}
}
if !marker {
if let Some(annotation) = lifecycle.first() {
if self.discovery.saw_lifecycle.is_none() {
self.discovery.saw_lifecycle = Some((annotation.clone(), site.path.clone()));
}
}
return Ok(Availability::full());
}
let Some(members) = site.enum_members else {
return Err(VersioningError::VersionOutsideEnum {
target: format!("{} ({})", site.path, site.kind.describe()),
}
.into());
};
if let Some(annotation) = lifecycle.first() {
return Err(VersioningError::VersionedVersionEnum {
annotation: annotation.clone(),
ident: site.path.clone(),
}
.into());
}
for member in members {
for annotation in &member.annotations {
if matches!(
annotation,
Annotation::Added { .. } | Annotation::Removed { .. }
) {
return Err(VersioningError::VersionedVersionEnum {
annotation: "@added/@removed".to_string(),
ident: format!("{}::{}", site.path, member.ident),
}
.into());
}
}
}
if let Some((first, _)) = &self.discovery.version_enum {
return Err(VersioningError::DuplicateVersionEnum {
first: first.clone(),
second: site.path.clone(),
}
.into());
}
self.discovery.version_enum = Some((site.path.clone(), members.to_vec()));
self.discovery.version_parameter = parameter;
Ok(Availability::full())
}
}
pub(crate) fn discover(definitions: &[Definition]) -> ParserResult<Discovery> {
let mut visitor = DiscoverVisitor {
discovery: Discovery {
version_enum: None,
saw_lifecycle: None,
version_parameter: None,
},
};
visitor.walk(definitions, "", Availability::full())?;
Ok(visitor.discovery)
}
pub(crate) struct LifecycleValidator<'a> {
pub(crate) timeline: &'a VersionTimeline,
}
impl VersionVisitor for LifecycleValidator<'_> {
fn enter(&mut self, site: &Site<'_>, parent: Availability) -> ParserResult<Availability> {
availability(site, parent, self.timeline)
}
}