use super::parse::segment_display;
use super::{AliasPolicy, PathError, PathSegment};
pub(super) fn registry_for(
node: &crate::yaml::SyntaxNode,
) -> crate::anchor_resolution::AnchorRegistry {
use crate::anchor_resolution::{document_scope, AnchorRegistry};
AnchorRegistry::from_tree(&document_scope(node))
}
pub(super) fn undefined_alias(
error: crate::anchor_resolution::AliasError,
segment: &PathSegment,
) -> PathError {
use crate::anchor_resolution::AliasError;
let (AliasError::Undefined { alias } | AliasError::Recursive { alias }) = error;
PathError::UndefinedAlias {
at: segment_display(segment),
alias,
}
}
pub(super) fn resolve_alias(
alias: &crate::yaml::Alias,
segment: &PathSegment,
policy: AliasPolicy,
) -> Result<crate::as_yaml::YamlNode, PathError> {
use rowan::ast::AstNode;
if policy == AliasPolicy::Refuse {
return Err(PathError::AliasRefused {
at: segment_display(segment),
alias: alias.name(),
});
}
let registry = registry_for(alias.syntax());
if policy != AliasPolicy::Expand {
return crate::anchor_resolution::resolve_alias_with(alias, ®istry, |t| t.clone())
.map_err(|e| undefined_alias(e, segment));
}
let undefined = || crate::anchor_resolution::AliasError::Undefined {
alias: alias.name(),
};
registry
.resolve_alias_through(alias)
.and_then(|target| expanded_copy(target, alias.syntax(), ®istry, &mut Vec::new()))
.and_then(|copy| crate::as_yaml::YamlNode::from_syntax_peeled(copy).ok_or_else(undefined))
.map_err(|e| undefined_alias(e, segment))
}
fn expanded_copy(
target: &crate::yaml::SyntaxNode,
outer: &crate::yaml::SyntaxNode,
registry: &crate::anchor_resolution::AnchorRegistry,
open: &mut Vec<crate::yaml::SyntaxNode>,
) -> Result<crate::yaml::SyntaxNode, crate::anchor_resolution::AliasError> {
use crate::anchor_resolution::AliasError;
use crate::lex::SyntaxKind;
use rowan::ast::AstNode;
let mut builder = rowan::GreenNodeBuilder::new();
builder.start_node(target.kind().into());
let base = crate::column::node_column(target) as isize;
crate::as_yaml::copy_node_content_reindent(&mut builder, target, -base);
builder.finish_node();
let copy = crate::yaml::SyntaxNode::new_root_mut(builder.finish());
let anchors: Vec<_> = copy
.descendants_with_tokens()
.filter_map(|e| e.into_token())
.filter(|t| t.kind() == SyntaxKind::ANCHOR)
.collect();
for anchor in anchors {
crate::nodes::detach_anchor(&anchor);
}
let aliases = |node: &crate::yaml::SyntaxNode| -> Vec<crate::yaml::SyntaxNode> {
node.descendants()
.filter(|n| n.kind() == SyntaxKind::ALIAS)
.collect()
};
let (originals, copies) = (aliases(target), aliases(©));
assert_eq!(
originals.len(),
copies.len(),
"a copy has the aliases of its original"
);
open.push(target.clone());
for (original, copied) in originals.into_iter().zip(copies) {
let original = crate::yaml::Alias::cast(original).expect("an ALIAS node is an alias");
let name = original.name();
let Some(referred) = registry.resolve_alias_at(&name, original.syntax()) else {
continue;
};
if registry.resolve_alias_at(&name, outer) == Some(referred) {
continue;
}
let node = registry.resolve_alias_through(&original)?;
let node = match node.kind() {
SyntaxKind::VALUE => node
.children()
.next()
.ok_or_else(|| AliasError::Undefined {
alias: name.clone(),
})?,
_ => node.clone(),
};
if open.contains(&node) {
return Err(AliasError::Recursive { alias: name });
}
let replacement = expanded_copy(&node, outer, registry, open)?;
crate::nodes::replace_alias_with_copy(&copied, &replacement);
}
open.pop();
Ok(copy)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expand_copy_keeps_tag_after_anchor() {
use crate::yaml::Document;
use std::str::FromStr;
for src in [
"a: !!map &x\n k: 1\nb: *x\n",
"a: &x !!map\n k: 1\nb: *x\n",
] {
let doc = Document::from_str(src).unwrap();
let Some(crate::as_yaml::YamlNode::Alias(alias)) = doc.as_mapping().unwrap().get("b")
else {
panic!("b is not an alias in {src:?}");
};
let segment = PathSegment::Key("b".to_string());
let copy = resolve_alias(&alias, &segment, AliasPolicy::Expand).unwrap();
assert_eq!(copy.to_string(), "!!map\nk: 1\n", "{src:?}");
}
}
}