yaml_edit/nodes/
alias_node.rs1use super::{Lang, SyntaxNode};
2use crate::as_yaml::{AsYaml, YamlKind};
3use crate::lex::SyntaxKind;
4use rowan::ast::AstNode;
5
6ast_node!(Alias, ALIAS, "A YAML alias reference (e.g., *anchor_name)");
7
8impl Alias {
9 pub fn value(&self) -> String {
11 self.0.text().to_string()
12 }
13
14 pub fn name(&self) -> String {
37 let text = self.value();
38 if let Some(stripped) = text.strip_prefix('*') {
40 stripped.to_string()
41 } else {
42 text
43 }
44 }
45}
46
47impl AsYaml for Alias {
48 fn as_node(&self) -> Option<&SyntaxNode> {
49 Some(&self.0)
50 }
51
52 fn kind(&self) -> YamlKind {
53 YamlKind::Alias
54 }
55
56 fn build_content(
57 &self,
58 builder: &mut rowan::GreenNodeBuilder,
59 _indent: usize,
60 _flow_context: bool,
61 ) -> bool {
62 crate::as_yaml::copy_node_content(builder, &self.0);
63 false
65 }
66
67 fn is_inline(&self) -> bool {
68 true
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::{Document, YamlNode};
75 use std::str::FromStr;
76
77 #[test]
78 fn test_simple_alias() {
79 let yaml = r#"
80anchor: &my_anchor value
81reference: *my_anchor
82"#;
83
84 let doc = Document::from_str(yaml).unwrap();
85 let mapping = doc.as_mapping().unwrap();
86
87 let reference = mapping
89 .get("reference")
90 .expect("Should have 'reference' key");
91
92 match reference {
93 YamlNode::Alias(alias) => {
94 assert_eq!(alias.name(), "my_anchor");
95 assert_eq!(alias.value(), "*my_anchor");
96 }
97 _ => panic!("Expected alias, got {:?}", reference),
98 }
99 }
100
101 #[test]
102 fn test_alias_in_sequence() {
103 let yaml = r#"
104colors:
105 - &red '#FF0000'
106 - &green '#00FF00'
107 - &blue '#0000FF'
108
109theme:
110 - *red
111 - *blue
112"#;
113
114 let doc = Document::from_str(yaml).unwrap();
115 let mapping = doc.as_mapping().unwrap();
116
117 let theme = mapping.get("theme").expect("Should have 'theme' key");
118 let theme_seq = theme.as_sequence().expect("Should be a sequence");
119 assert_eq!(theme_seq.len(), 2);
120
121 let first = theme_seq.get(0).unwrap();
123 match first {
124 YamlNode::Alias(alias) => {
125 assert_eq!(alias.name(), "red");
126 }
127 _ => panic!("Expected alias, got {:?}", first),
128 }
129
130 let second = theme_seq.get(1).unwrap();
132 match second {
133 YamlNode::Alias(alias) => {
134 assert_eq!(alias.name(), "blue");
135 }
136 _ => panic!("Expected alias, got {:?}", second),
137 }
138 }
139
140 #[test]
141 fn test_alias_in_mapping() {
142 let yaml = r#"
143defaults: &defaults
144 adapter: postgres
145 host: localhost
146
147development:
148 database: dev_db
149 <<: *defaults
150"#;
151
152 let doc = Document::from_str(yaml).unwrap();
153 let mapping = doc.as_mapping().unwrap();
154
155 let development = mapping
156 .get("development")
157 .expect("Should have 'development' key");
158 let dev_mapping = development.as_mapping().expect("Should be a mapping");
159
160 let merge_entry = dev_mapping
162 .iter()
163 .find(|(k, _)| {
164 k.as_scalar()
165 .map(|s| s.as_string() == "<<")
166 .unwrap_or(false)
167 })
168 .expect("Should have merge key entry");
169
170 match merge_entry.1 {
172 YamlNode::Alias(alias) => {
173 assert_eq!(alias.name(), "defaults");
174 assert_eq!(alias.value(), "*defaults");
175 }
176 _ => panic!("Expected alias for merge key, got {:?}", merge_entry.1),
177 }
178 }
179
180 #[test]
181 fn test_multiple_aliases_to_same_anchor() {
182 let yaml = r#"
183value: &shared 42
184first: *shared
185second: *shared
186third: *shared
187"#;
188
189 let doc = Document::from_str(yaml).unwrap();
190 let mapping = doc.as_mapping().unwrap();
191
192 for key in &["first", "second", "third"] {
193 let node = mapping
194 .get(key)
195 .unwrap_or_else(|| panic!("Should have '{}' key", key));
196 match node {
197 YamlNode::Alias(alias) => {
198 assert_eq!(alias.name(), "shared");
199 }
200 _ => panic!("Expected alias for '{}', got {:?}", key, node),
201 }
202 }
203 }
204
205 #[test]
206 fn test_alias_round_trip() {
207 let yaml = r#"anchor: &test value
208reference: *test"#;
209
210 let doc = Document::from_str(yaml).unwrap();
211 let output = doc.to_string();
212
213 assert_eq!(output, yaml);
215 }
216
217 #[test]
218 fn test_alias_with_complex_anchor_name() {
219 let yaml = r#"
220data: &complex_anchor_name_123 some_value
221ref: *complex_anchor_name_123
222"#;
223
224 let doc = Document::from_str(yaml).unwrap();
225 let mapping = doc.as_mapping().unwrap();
226
227 let ref_node = mapping.get("ref").expect("Should have 'ref' key");
228 match ref_node {
229 YamlNode::Alias(alias) => {
230 assert_eq!(alias.name(), "complex_anchor_name_123");
231 }
232 _ => panic!("Expected alias, got {:?}", ref_node),
233 }
234 }
235
236 #[test]
237 fn test_nested_structure_with_aliases() {
238 let yaml = r#"
239base: &base
240 x: 1
241 y: 2
242
243items:
244 - name: first
245 config: *base
246 - name: second
247 config: *base
248"#;
249
250 let doc = Document::from_str(yaml).unwrap();
251 let mapping = doc.as_mapping().unwrap();
252
253 let items = mapping.get("items").expect("Should have 'items' key");
254 let items_seq = items.as_sequence().expect("Should be a sequence");
255 assert_eq!(items_seq.len(), 2);
256
257 for i in 0..2 {
259 let item = items_seq.get(i).unwrap();
260 let item_mapping = item.as_mapping().expect("Should be a mapping");
261 let config = item_mapping
262 .get("config")
263 .expect("Should have 'config' key");
264
265 match config {
266 YamlNode::Alias(alias) => {
267 assert_eq!(alias.name(), "base");
268 }
269 _ => panic!("Expected alias for item {}, got {:?}", i, config),
270 }
271 }
272 }
273
274 #[test]
275 fn test_alias_preserves_whitespace() {
276 let yaml = "reference: *test ";
278
279 let doc = Document::from_str(yaml).unwrap();
280 let output = doc.to_string();
281
282 assert_eq!(output, yaml);
284 }
285}