1use crate::yaml::{Alias, Mapping, Scalar, Sequence, SyntaxNode, TaggedNode};
23use rowan::ast::AstNode;
24use std::borrow::Cow;
25use std::fmt;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum YamlKind {
30 Mapping,
32 Sequence,
34 Scalar,
36 Alias,
38 Document,
40 Tagged(Cow<'static, str>),
44}
45
46pub trait AsYaml {
62 fn as_node(&self) -> Option<&SyntaxNode>;
67
68 fn kind(&self) -> YamlKind;
70
71 fn build_content(
79 &self,
80 builder: &mut rowan::GreenNodeBuilder,
81 indent: usize,
82 flow_context: bool,
83 ) -> bool;
84
85 fn is_inline(&self) -> bool;
90}
91
92pub fn yaml_eq<A, B>(a: &A, b: &B) -> bool
98where
99 A: AsYaml + ?Sized,
100 B: AsYaml + ?Sized,
101{
102 if let Some(node) = a.as_node() {
105 return node_eq_rhs(node, b);
106 }
107 if let Some(node) = b.as_node() {
108 return node_eq_rhs(node, a);
109 }
110
111 if a.kind() != b.kind() {
113 return false;
114 }
115 match (raw_scalar_str(a), raw_scalar_str(b)) {
116 (Some(sa), Some(sb)) => sa == sb,
117 _ => false,
118 }
119}
120
121fn node_eq_rhs<B: AsYaml + ?Sized>(node: &SyntaxNode, rhs: &B) -> bool {
123 use crate::lex::SyntaxKind;
124 match node.kind() {
125 SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| scalar_eq_rhs(&s, rhs)),
126 SyntaxKind::MAPPING => Mapping::cast(node.clone()).is_some_and(|m| mapping_eq_rhs(&m, rhs)),
127 SyntaxKind::SEQUENCE => {
128 Sequence::cast(node.clone()).is_some_and(|s| sequence_eq_rhs(&s, rhs))
129 }
130 SyntaxKind::TAGGED_NODE => {
131 TaggedNode::cast(node.clone()).is_some_and(|t| tagged_eq_rhs(&t, rhs))
132 }
133 _ => false,
134 }
135}
136
137fn raw_scalar_str<T: AsYaml + ?Sized>(v: &T) -> Option<String> {
139 if v.as_node().is_some() || v.kind() != YamlKind::Scalar {
140 return None;
141 }
142 let mut builder = rowan::GreenNodeBuilder::new();
143 v.build_content(&mut builder, 0, false);
144 let green = builder.finish();
145 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
146 Scalar::cast(node.clone())
147 .map(|s| s.as_string())
148 .or_else(|| Some(node.text().to_string()))
149}
150
151pub(crate) fn scalar_semantic_value(scalar: &Scalar) -> Option<(crate::lex::SyntaxKind, String)> {
158 use crate::lex::SyntaxKind;
159 use crate::scalar::ScalarValue;
160
161 let token = scalar.0.first_token()?;
163 let mut kind = token.kind();
164 let text = token.text();
165
166 let extra_content = scalar
174 .0
175 .children_with_tokens()
176 .filter_map(|c| c.into_token())
177 .filter(|t| !t.text().is_empty())
178 .nth(1)
179 .is_some();
180 if extra_content {
181 kind = SyntaxKind::STRING;
182 }
183
184 let normalized = match kind {
185 SyntaxKind::INT => {
186 ScalarValue::parse_integer(text).map_or_else(|| text.to_string(), |v| v.to_string())
188 }
189 SyntaxKind::FLOAT => {
190 text.parse::<f64>()
192 .map_or_else(|_| text.to_string(), |v| v.to_string())
193 }
194 SyntaxKind::BOOL => {
195 text.to_lowercase()
197 }
198 SyntaxKind::NULL => {
199 "null".to_string()
201 }
202 SyntaxKind::STRING => {
203 scalar.as_string()
205 }
206 _ => {
207 scalar.as_string()
209 }
210 };
211
212 Some((kind, normalized))
213}
214
215fn scalar_eq_rhs<B: AsYaml + ?Sized>(lhs: &Scalar, rhs: &B) -> bool {
216 let rhs_scalar = if let Some(node) = rhs.as_node() {
218 let Some(scalar) = Scalar::cast(node.clone()) else {
219 return false;
220 };
221 scalar
222 } else {
223 if rhs.kind() != YamlKind::Scalar {
225 return false;
226 }
227 let mut builder = rowan::GreenNodeBuilder::new();
231 rhs.build_content(&mut builder, 0, true);
232 let green = builder.finish();
233 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
234 let Some(scalar) = Scalar::cast(node) else {
235 return false;
236 };
237 scalar
238 };
239
240 let Some((lhs_kind, lhs_value)) = scalar_semantic_value(lhs) else {
242 return false;
243 };
244 let Some((rhs_kind, rhs_value)) = scalar_semantic_value(&rhs_scalar) else {
245 return false;
246 };
247
248 lhs_kind == rhs_kind && lhs_value == rhs_value
252}
253
254fn mapping_eq_rhs<B: AsYaml + ?Sized>(lhs: &Mapping, rhs: &B) -> bool {
255 let Some(node) = rhs.as_node() else {
256 return false;
257 };
258 let Some(r) = Mapping::cast(node.clone()) else {
259 return false;
260 };
261 let lhs_pairs: Vec<_> = lhs.pairs().collect();
262 let rhs_pairs: Vec<_> = r.pairs().collect();
263 if lhs_pairs.len() != rhs_pairs.len() {
264 return false;
265 }
266 lhs_pairs
268 .iter()
269 .zip(rhs_pairs.iter())
270 .all(|((lk, lv), (rk, rv))| {
271 let Some(lk) = YamlNode::from_syntax_peeled(lk.clone()) else {
272 return false;
273 };
274 let Some(rk) = YamlNode::from_syntax_peeled(rk.clone()) else {
275 return false;
276 };
277 let Some(lv) = YamlNode::from_syntax_peeled(lv.clone()) else {
278 return false;
279 };
280 let Some(rv) = YamlNode::from_syntax_peeled(rv.clone()) else {
281 return false;
282 };
283 yaml_eq(&lk, &rk) && yaml_eq(&lv, &rv)
284 })
285}
286
287fn sequence_eq_rhs<B: AsYaml + ?Sized>(lhs: &Sequence, rhs: &B) -> bool {
288 let Some(node) = rhs.as_node() else {
289 return false;
290 };
291 let Some(r) = Sequence::cast(node.clone()) else {
292 return false;
293 };
294 let lhs_items: Vec<_> = lhs.items().collect();
295 let rhs_items: Vec<_> = r.items().collect();
296 if lhs_items.len() != rhs_items.len() {
297 return false;
298 }
299 lhs_items.iter().zip(rhs_items.iter()).all(|(l, r)| {
301 match (
302 YamlNode::from_syntax(l.clone()),
303 YamlNode::from_syntax(r.clone()),
304 ) {
305 (Some(l), Some(r)) => yaml_eq(&l, &r),
306 _ => false,
307 }
308 })
309}
310
311fn tagged_eq_rhs<B: AsYaml + ?Sized>(lhs: &TaggedNode, rhs: &B) -> bool {
312 let Some(node) = rhs.as_node() else {
313 return false;
314 };
315 TaggedNode::cast(node.clone())
316 .is_some_and(|r| lhs.tag() == r.tag() && lhs.as_string() == r.as_string())
317}
318
319#[derive(Debug, Clone, PartialEq)]
346pub enum YamlNode {
347 Scalar(Scalar),
349 Mapping(Mapping),
351 Sequence(Sequence),
353 Alias(Alias),
355 TaggedNode(TaggedNode),
357}
358
359impl YamlNode {
360 pub fn from_syntax(node: SyntaxNode) -> Option<Self> {
365 use crate::lex::SyntaxKind;
366 match node.kind() {
367 SyntaxKind::SCALAR => Scalar::cast(node).map(YamlNode::Scalar),
368 SyntaxKind::MAPPING => Mapping::cast(node).map(YamlNode::Mapping),
369 SyntaxKind::SEQUENCE => Sequence::cast(node).map(YamlNode::Sequence),
370 SyntaxKind::ALIAS => Alias::cast(node).map(YamlNode::Alias),
371 SyntaxKind::TAGGED_NODE => TaggedNode::cast(node).map(YamlNode::TaggedNode),
372 _ => None,
373 }
374 }
375
376 pub(crate) fn from_syntax_peeled(node: SyntaxNode) -> Option<Self> {
382 use crate::lex::SyntaxKind;
383 let inner = if matches!(node.kind(), SyntaxKind::KEY | SyntaxKind::VALUE) {
384 node.children().next()?
385 } else {
386 node
387 };
388 Self::from_syntax(inner)
389 }
390
391 pub fn kind(&self) -> YamlKind {
393 match self {
394 YamlNode::Scalar(_) => YamlKind::Scalar,
395 YamlNode::Mapping(_) => YamlKind::Mapping,
396 YamlNode::Sequence(_) => YamlKind::Sequence,
397 YamlNode::Alias(_) => YamlKind::Alias,
398 YamlNode::TaggedNode(t) => t
399 .tag()
400 .map_or(YamlKind::Scalar, |tag| YamlKind::Tagged(Cow::Owned(tag))),
401 }
402 }
403
404 pub(crate) fn syntax(&self) -> &SyntaxNode {
406 match self {
407 YamlNode::Scalar(s) => s.syntax(),
408 YamlNode::Mapping(m) => m.syntax(),
409 YamlNode::Sequence(s) => s.syntax(),
410 YamlNode::Alias(a) => a.syntax(),
411 YamlNode::TaggedNode(t) => t.syntax(),
412 }
413 }
414
415 pub fn yaml_eq<O: AsYaml>(&self, other: &O) -> bool {
417 yaml_eq(self, other)
418 }
419
420 pub fn as_scalar(&self) -> Option<&Scalar> {
422 if let YamlNode::Scalar(s) = self {
423 Some(s)
424 } else {
425 None
426 }
427 }
428
429 pub fn as_mapping(&self) -> Option<&Mapping> {
431 if let YamlNode::Mapping(m) = self {
432 Some(m)
433 } else {
434 None
435 }
436 }
437
438 pub fn as_sequence(&self) -> Option<&Sequence> {
440 if let YamlNode::Sequence(s) = self {
441 Some(s)
442 } else {
443 None
444 }
445 }
446
447 pub fn as_tagged(&self) -> Option<&TaggedNode> {
449 if let YamlNode::TaggedNode(t) = self {
450 Some(t)
451 } else {
452 None
453 }
454 }
455
456 pub fn as_alias(&self) -> Option<&Alias> {
458 if let YamlNode::Alias(a) = self {
459 Some(a)
460 } else {
461 None
462 }
463 }
464
465 pub fn is_scalar(&self) -> bool {
467 matches!(self, YamlNode::Scalar(_))
468 }
469
470 pub fn is_mapping(&self) -> bool {
472 matches!(self, YamlNode::Mapping(_))
473 }
474
475 pub fn is_sequence(&self) -> bool {
477 matches!(self, YamlNode::Sequence(_))
478 }
479
480 pub fn is_tagged(&self) -> bool {
482 matches!(self, YamlNode::TaggedNode(_))
483 }
484
485 pub fn is_alias(&self) -> bool {
487 matches!(self, YamlNode::Alias(_))
488 }
489
490 pub fn to_i64(&self) -> Option<i64> {
494 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_i64()
495 }
496
497 pub fn to_f64(&self) -> Option<f64> {
501 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_f64()
502 }
503
504 pub fn to_bool(&self) -> Option<bool> {
508 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_bool()
509 }
510
511 pub fn get(&self, key: impl crate::AsYaml) -> Option<YamlNode> {
515 self.as_mapping()?
516 .get_node(key)
517 .and_then(YamlNode::from_syntax)
518 }
519
520 pub fn get_item(&self, index: usize) -> Option<YamlNode> {
524 self.as_sequence()?
525 .items()
526 .nth(index)
527 .and_then(YamlNode::from_syntax)
528 }
529}
530
531impl AsYaml for YamlNode {
532 fn as_node(&self) -> Option<&SyntaxNode> {
533 Some(self.syntax())
534 }
535
536 fn kind(&self) -> YamlKind {
537 YamlNode::kind(self)
538 }
539
540 fn build_content(
541 &self,
542 builder: &mut rowan::GreenNodeBuilder,
543 indent: usize,
544 flow_context: bool,
545 ) -> bool {
546 match self {
547 YamlNode::Scalar(s) => s.build_content(builder, indent, flow_context),
548 YamlNode::Mapping(m) => m.build_content(builder, indent, flow_context),
549 YamlNode::Sequence(s) => s.build_content(builder, indent, flow_context),
550 YamlNode::Alias(a) => {
551 let node = a.syntax();
552 builder.start_node(node.kind().into());
553 let inner = a.build_content(builder, indent, flow_context);
554 builder.finish_node();
555 inner
556 }
557 YamlNode::TaggedNode(t) => {
558 let node = t.syntax();
559 builder.start_node(node.kind().into());
560 let inner = t.build_content(builder, indent, flow_context);
561 builder.finish_node();
562 inner
563 }
564 }
565 }
566
567 fn is_inline(&self) -> bool {
568 use crate::yaml::ValueNode;
569 match self {
570 YamlNode::Scalar(s) => ValueNode::is_inline(s),
571 YamlNode::Mapping(m) => ValueNode::is_inline(m),
572 YamlNode::Sequence(s) => ValueNode::is_inline(s),
573 YamlNode::Alias(_) => true,
574 YamlNode::TaggedNode(t) => ValueNode::is_inline(t),
575 }
576 }
577}
578
579impl PartialEq<str> for YamlNode {
581 fn eq(&self, other: &str) -> bool {
582 yaml_eq(self, &other)
583 }
584}
585
586impl PartialEq<&str> for YamlNode {
587 fn eq(&self, other: &&str) -> bool {
588 yaml_eq(self, other)
589 }
590}
591
592impl PartialEq<String> for YamlNode {
593 fn eq(&self, other: &String) -> bool {
594 yaml_eq(self, &other.as_str())
595 }
596}
597
598impl fmt::Display for YamlNode {
599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600 write!(f, "{}", self.syntax().text())
601 }
602}
603
604impl<T: AsYaml + ?Sized> AsYaml for &T {
611 fn as_node(&self) -> Option<&SyntaxNode> {
612 (*self).as_node()
613 }
614
615 fn kind(&self) -> YamlKind {
616 (*self).kind()
617 }
618
619 fn build_content(
620 &self,
621 builder: &mut rowan::GreenNodeBuilder,
622 indent: usize,
623 flow_context: bool,
624 ) -> bool {
625 (*self).build_content(builder, indent, flow_context)
626 }
627
628 fn is_inline(&self) -> bool {
629 (*self).is_inline()
630 }
631}
632
633pub(crate) fn copy_node_content(builder: &mut rowan::GreenNodeBuilder, node: &SyntaxNode) {
635 for child in node.children_with_tokens() {
636 match child {
637 rowan::NodeOrToken::Node(n) => {
638 builder.start_node(n.kind().into());
639 copy_node_content(builder, &n);
640 builder.finish_node();
641 }
642 rowan::NodeOrToken::Token(t) => {
643 builder.token(t.kind().into(), t.text());
644 }
645 }
646 }
647}
648
649pub(crate) fn copy_node_content_with_indent(
655 builder: &mut rowan::GreenNodeBuilder,
656 node: &SyntaxNode,
657 indent: usize,
658) {
659 use crate::lex::SyntaxKind;
660 let mut after_newline = false;
661
662 for child in node.children_with_tokens() {
663 match child {
664 rowan::NodeOrToken::Node(n) => {
665 if after_newline && indent > 0 {
667 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
668 }
669 builder.start_node(n.kind().into());
670 copy_node_content_with_indent(builder, &n, indent);
671 builder.finish_node();
672 after_newline = false;
673 }
674 rowan::NodeOrToken::Token(t) => {
675 match t.kind() {
676 SyntaxKind::NEWLINE => {
677 builder.token(t.kind().into(), t.text());
678 after_newline = true;
679 }
680 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
681 if after_newline && indent > 0 {
683 let existing_indent = t.text().len();
684 let total_indent = indent + existing_indent;
685 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(total_indent));
686 } else if after_newline {
687 } else {
689 builder.token(t.kind().into(), t.text());
691 }
692 after_newline = false;
693 }
694 SyntaxKind::DASH => {
695 if after_newline && indent > 0 {
697 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
698 }
699 builder.token(t.kind().into(), t.text());
700 after_newline = false;
701 }
702 _ => {
703 if after_newline && indent > 0 {
705 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
706 }
707 builder.token(t.kind().into(), t.text());
708 after_newline = false;
709 }
710 }
711 }
712 }
713 }
714}
715
716pub(crate) fn copy_node_content_reindent(
726 builder: &mut rowan::GreenNodeBuilder,
727 node: &SyntaxNode,
728 delta: isize,
729) -> bool {
730 use crate::lex::SyntaxKind;
731 let mut after_newline = false;
732 let pad = |n: isize| " ".repeat(n.max(0) as usize);
733
734 for child in node.children_with_tokens() {
735 match child {
736 rowan::NodeOrToken::Node(n) => {
737 if n.text_range().is_empty() {
741 builder.start_node(n.kind().into());
742 builder.finish_node();
743 continue;
744 }
745 if after_newline && delta > 0 {
746 builder.token(SyntaxKind::WHITESPACE.into(), &pad(delta));
747 }
748 builder.start_node(n.kind().into());
749 after_newline = copy_node_content_reindent(builder, &n, delta);
750 builder.finish_node();
751 }
752 rowan::NodeOrToken::Token(t) => match t.kind() {
753 SyntaxKind::NEWLINE => {
754 builder.token(t.kind().into(), t.text());
755 after_newline = true;
756 }
757 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
758 if after_newline {
759 let total = t.text().len() as isize + delta;
760 if total > 0 {
761 builder.token(SyntaxKind::WHITESPACE.into(), &pad(total));
762 }
763 } else {
764 builder.token(t.kind().into(), t.text());
765 }
766 after_newline = false;
767 }
768 _ => {
769 if after_newline && delta > 0 {
770 builder.token(SyntaxKind::WHITESPACE.into(), &pad(delta));
771 }
772 builder.token(t.kind().into(), t.text());
773 after_newline = false;
774 }
775 },
776 }
777 }
778 after_newline
779}
780
781pub(crate) fn source_base_indent(node: &SyntaxNode) -> usize {
787 let mut column: usize = 0;
794 let mut cursor = node.prev_sibling_or_token();
795 let mut current = node.clone();
796 loop {
797 while let Some(item) = cursor {
798 match item {
799 rowan::NodeOrToken::Token(t) => {
800 let text = t.text();
801 if let Some(nl) = text.rfind('\n') {
802 column += text[nl + 1..].chars().count();
803 return column;
804 }
805 column += text.chars().count();
806 cursor = t.prev_sibling_or_token();
807 }
808 rowan::NodeOrToken::Node(n) => {
809 let text = n.text().to_string();
813 if let Some(nl) = text.rfind('\n') {
814 column += text[nl + 1..].chars().count();
815 return column;
816 }
817 column += text.chars().count();
818 cursor = n.prev_sibling_or_token();
819 }
820 }
821 }
822 match current.parent() {
824 Some(p) => {
825 cursor = p.prev_sibling_or_token();
826 current = p;
827 }
828 None => return column,
829 }
830 }
831}
832
833macro_rules! impl_as_yaml_int {
839 ($($ty:ty),+ $(,)?) => {
840 $(impl AsYaml for $ty {
841 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
842 None
843 }
844
845 fn kind(&self) -> YamlKind {
846 YamlKind::Scalar
847 }
848
849 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
850 use crate::lex::SyntaxKind;
851 builder.start_node(SyntaxKind::SCALAR.into());
852 builder.token(SyntaxKind::INT.into(), &self.to_string());
853 builder.finish_node();
854 false
855 }
856
857 fn is_inline(&self) -> bool {
858 true
859 }
860 })+
861 };
862}
863
864macro_rules! impl_as_yaml_float {
866 ($($ty:ty),+ $(,)?) => {
867 $(impl AsYaml for $ty {
868 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
869 None
870 }
871
872 fn kind(&self) -> YamlKind {
873 YamlKind::Scalar
874 }
875
876 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
877 use crate::lex::SyntaxKind;
878 builder.start_node(SyntaxKind::SCALAR.into());
879 let s = self.to_string();
880 let float_str = if s.contains('.') || s.contains('e') || s.contains('E') {
882 s
883 } else {
884 format!("{}.0", s)
885 };
886 builder.token(SyntaxKind::FLOAT.into(), &float_str);
887 builder.finish_node();
888 false
889 }
890
891 fn is_inline(&self) -> bool {
892 true
893 }
894 })+
895 };
896}
897
898impl_as_yaml_int!(i64, i32, i16, i8, isize, u64, u32, u16, u8, usize);
900
901impl_as_yaml_float!(f64, f32);
903
904impl AsYaml for bool {
905 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
906 None
907 }
908
909 fn kind(&self) -> YamlKind {
910 YamlKind::Scalar
911 }
912
913 fn build_content(
914 &self,
915 builder: &mut rowan::GreenNodeBuilder,
916 _indent: usize,
917 _flow_context: bool,
918 ) -> bool {
919 use crate::lex::SyntaxKind;
920 builder.start_node(SyntaxKind::SCALAR.into());
921 builder.token(
922 SyntaxKind::BOOL.into(),
923 if *self { "true" } else { "false" },
924 );
925 builder.finish_node();
926 false
927 }
928
929 fn is_inline(&self) -> bool {
930 true
931 }
932}
933
934impl AsYaml for String {
935 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
936 None
937 }
938
939 fn kind(&self) -> YamlKind {
940 YamlKind::Scalar
941 }
942
943 fn build_content(
944 &self,
945 builder: &mut rowan::GreenNodeBuilder,
946 indent: usize,
947 flow_context: bool,
948 ) -> bool {
949 self.as_str().build_content(builder, indent, flow_context)
950 }
951
952 fn is_inline(&self) -> bool {
953 true
954 }
955}
956
957impl AsYaml for &str {
958 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
959 None
960 }
961
962 fn kind(&self) -> YamlKind {
963 YamlKind::Scalar
964 }
965
966 fn build_content(
967 &self,
968 builder: &mut rowan::GreenNodeBuilder,
969 indent: usize,
970 flow_context: bool,
971 ) -> bool {
972 use crate::lex::SyntaxKind;
973 use crate::scalar::ScalarValue;
974
975 let scalar = if flow_context {
978 ScalarValue::double_quoted(*self)
979 } else {
980 ScalarValue::string(*self)
981 };
982
983 let yaml_text = scalar.to_yaml_string_with_indent(indent + 2);
986 builder.start_node(SyntaxKind::SCALAR.into());
989 builder.token(SyntaxKind::STRING.into(), &yaml_text);
990 builder.finish_node();
991 false
992 }
993
994 fn is_inline(&self) -> bool {
995 true
996 }
997}
998
999impl<T: AsYaml> AsYaml for Option<T> {
1001 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
1002 self.as_ref().and_then(AsYaml::as_node)
1003 }
1004
1005 fn kind(&self) -> YamlKind {
1006 match self {
1007 Some(v) => v.kind(),
1008 None => YamlKind::Scalar,
1009 }
1010 }
1011
1012 fn build_content(
1013 &self,
1014 builder: &mut rowan::GreenNodeBuilder,
1015 indent: usize,
1016 flow_context: bool,
1017 ) -> bool {
1018 match self {
1019 Some(v) => v.build_content(builder, indent, flow_context),
1020 None => crate::scalar::ScalarValue::null().build_content(builder, indent, flow_context),
1021 }
1022 }
1023
1024 fn is_inline(&self) -> bool {
1025 match self {
1026 Some(v) => v.is_inline(),
1027 None => true,
1028 }
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::yaml::{Document, YamlFile};
1036 use std::str::FromStr;
1037
1038 #[test]
1039 fn test_mapping_set_preserves_alias_kind() {
1040 let doc = Document::from_str("shared: &shared value\na: *shared\nb: old\n").unwrap();
1041 let mapping = doc.as_mapping().unwrap();
1042 mapping.set("b", mapping.get("a").unwrap());
1043 assert_eq!(
1044 doc.to_string(),
1045 "shared: &shared value\na: *shared\nb: *shared\n"
1046 );
1047
1048 match mapping.get("b").unwrap() {
1049 YamlNode::Alias(alias) => {
1050 assert_eq!(alias.name(), "shared");
1051 assert_eq!(alias.value(), "*shared");
1052 }
1053 other => panic!("Expected alias, got {:?}", other),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_yaml_eq_different_quoting_styles() {
1059 let yaml = r#"
1060plain: value
1061single: 'value'
1062double: "value"
1063"#;
1064
1065 let doc = Document::from_str(yaml).unwrap();
1066 let mapping = doc.as_mapping().unwrap();
1067
1068 let plain = mapping.get("plain").unwrap();
1069 let single = mapping.get("single").unwrap();
1070 let double = mapping.get("double").unwrap();
1071
1072 assert!(yaml_eq(&plain, &single));
1074 assert!(yaml_eq(&single, &double));
1075 assert!(yaml_eq(&plain, &double));
1076
1077 assert!(yaml_eq(&plain, &"value"));
1079 assert!(yaml_eq(&single, &"value"));
1080 assert!(yaml_eq(&double, &"value"));
1081 }
1082
1083 #[test]
1084 fn test_yaml_eq_escape_sequences() {
1085 let yaml = r#"
1086newline1: "line1\nline2"
1087newline2: "line1
1088line2"
1089tab1: "a\tb"
1090tab2: "a b"
1091backslash1: "path\\file"
1092backslash2: 'path\file'
1093quote1: "say \"hi\""
1094quote2: 'say "hi"'
1095"#;
1096
1097 let doc = Document::from_str(yaml).unwrap();
1098 let mapping = doc.as_mapping().unwrap();
1099
1100 let newline1 = mapping.get("newline1").unwrap();
1101 let newline2 = mapping.get("newline2").unwrap();
1102 let tab1 = mapping.get("tab1").unwrap();
1103 let tab2 = mapping.get("tab2").unwrap();
1104 let backslash1 = mapping.get("backslash1").unwrap();
1105 let backslash2 = mapping.get("backslash2").unwrap();
1106 let quote1 = mapping.get("quote1").unwrap();
1107 let quote2 = mapping.get("quote2").unwrap();
1108
1109 assert!(yaml_eq(&newline1, &newline2));
1111
1112 assert!(yaml_eq(&tab1, &tab2));
1114
1115 assert!(yaml_eq(&backslash1, &backslash2));
1120 assert!(yaml_eq(&backslash1, &"path\\file"));
1121 assert!(yaml_eq(&backslash2, &"path\\file"));
1122
1123 assert!(yaml_eq("e1, "e2));
1125 assert!(yaml_eq("e1, &r#"say "hi""#));
1126 }
1127
1128 #[test]
1129 fn test_yaml_eq_single_quote_escaping() {
1130 let yaml = r#"
1131single1: 'can''t'
1132single2: "can't"
1133"#;
1134
1135 let doc = Document::from_str(yaml).unwrap();
1136 let mapping = doc.as_mapping().unwrap();
1137
1138 let single1 = mapping.get("single1").unwrap();
1139 let single2 = mapping.get("single2").unwrap();
1140
1141 assert!(yaml_eq(&single1, &single2));
1143 assert!(yaml_eq(&single1, &"can't"));
1144 }
1145
1146 #[test]
1147 fn test_yaml_eq_unicode_escapes() {
1148 let yaml = r#"
1149unicode1: "hello\x20world"
1150unicode2: "hello world"
1151unicode3: "smiley\u0020face"
1152unicode4: "smiley face"
1153"#;
1154
1155 let doc = Document::from_str(yaml).unwrap();
1156 let mapping = doc.as_mapping().unwrap();
1157
1158 let unicode1 = mapping.get("unicode1").unwrap();
1159 let unicode2 = mapping.get("unicode2").unwrap();
1160 let unicode3 = mapping.get("unicode3").unwrap();
1161 let unicode4 = mapping.get("unicode4").unwrap();
1162
1163 assert!(yaml_eq(&unicode1, &unicode2));
1165 assert!(yaml_eq(&unicode1, &"hello world"));
1166
1167 assert!(yaml_eq(&unicode3, &unicode4));
1169 assert!(yaml_eq(&unicode3, &"smiley face"));
1170 }
1171
1172 #[test]
1173 fn test_yaml_eq_with_comments() {
1174 let yaml1 = r#"
1175# This is a comment
1176key: value # inline comment
1177"#;
1178
1179 let yaml2 = r#"
1180key: value
1181"#;
1182
1183 let doc1 = YamlFile::from_str(yaml1).unwrap().document().unwrap();
1186 let doc2 = Document::from_str(yaml2).unwrap();
1187
1188 let mapping1 = doc1.as_mapping().unwrap();
1189 let mapping2 = doc2.as_mapping().unwrap();
1190
1191 assert!(yaml_eq(&mapping1, &mapping2));
1193
1194 let value1 = mapping1.get("key").unwrap();
1195 let value2 = mapping2.get("key").unwrap();
1196
1197 assert!(yaml_eq(&value1, &value2));
1198 assert!(yaml_eq(&value1, &"value"));
1199 }
1200
1201 #[test]
1202 fn test_yaml_eq_mappings() {
1203 let yaml1 = r#"
1204a: 1
1205b: 2
1206c: 3
1207"#;
1208
1209 let yaml2 = r#"
1210a: 1
1211b: 2
1212c: 3
1213"#;
1214
1215 let yaml3 = r#"
1216a: 1
1217c: 3
1218b: 2
1219"#;
1220
1221 let doc1 = Document::from_str(yaml1).unwrap();
1222 let doc2 = Document::from_str(yaml2).unwrap();
1223 let doc3 = Document::from_str(yaml3).unwrap();
1224
1225 let mapping1 = doc1.as_mapping().unwrap();
1226 let mapping2 = doc2.as_mapping().unwrap();
1227 let mapping3 = doc3.as_mapping().unwrap();
1228
1229 assert!(yaml_eq(&mapping1, &mapping2));
1231
1232 assert!(!yaml_eq(&mapping1, &mapping3));
1234 }
1235
1236 #[test]
1237 fn test_yaml_eq_sequences() {
1238 let yaml1 = r#"
1239- one
1240- two
1241- three
1242"#;
1243
1244 let yaml2 = r#"
1245- one
1246- two
1247- three
1248"#;
1249
1250 let yaml3 = r#"
1251- one
1252- three
1253- two
1254"#;
1255
1256 let doc1 = Document::from_str(yaml1).unwrap();
1257 let doc2 = Document::from_str(yaml2).unwrap();
1258 let doc3 = Document::from_str(yaml3).unwrap();
1259
1260 let seq1 = doc1.as_sequence().unwrap();
1261 let seq2 = doc2.as_sequence().unwrap();
1262 let seq3 = doc3.as_sequence().unwrap();
1263
1264 assert!(yaml_eq(&seq1, &seq2));
1266
1267 assert!(!yaml_eq(&seq1, &seq3));
1269 }
1270
1271 #[test]
1272 fn test_yaml_eq_nested_structures() {
1273 let yaml1 = r#"
1274outer:
1275 inner:
1276 key: "value"
1277"#;
1278
1279 let yaml2 = r#"
1280outer:
1281 inner:
1282 key: 'value'
1283"#;
1284
1285 let doc1 = Document::from_str(yaml1).unwrap();
1286 let doc2 = Document::from_str(yaml2).unwrap();
1287
1288 let mapping1 = doc1.as_mapping().unwrap();
1289 let mapping2 = doc2.as_mapping().unwrap();
1290
1291 assert!(yaml_eq(&mapping1, &mapping2));
1293 }
1294
1295 #[test]
1296 fn test_yaml_eq_special_characters() {
1297 let yaml = r#"
1298bell: "\a"
1299escape: "\e"
1300"null": "\0"
1301backspace: "\b"
1302formfeed: "\f"
1303carriagereturn: "\r"
1304verticaltab: "\v"
1305"#;
1306
1307 let doc = Document::from_str(yaml).unwrap();
1308 let mapping = doc.as_mapping().unwrap();
1309
1310 assert!(yaml_eq(&mapping.get("bell").unwrap(), &"\x07"));
1313 assert!(yaml_eq(&mapping.get("escape").unwrap(), &"\x1B"));
1314 assert!(yaml_eq(&mapping.get("null").unwrap(), &"\0"));
1315 assert!(yaml_eq(&mapping.get("backspace").unwrap(), &"\x08"));
1316 assert!(yaml_eq(&mapping.get("formfeed").unwrap(), &"\x0C"));
1317 assert!(yaml_eq(&mapping.get("carriagereturn").unwrap(), &"\r"));
1318 assert!(yaml_eq(&mapping.get("verticaltab").unwrap(), &"\x0B"));
1319 }
1320
1321 #[test]
1322 fn test_yaml_eq_empty_strings() {
1323 let yaml = r#"
1324empty1: ""
1325empty2: ''
1326"#;
1327
1328 let doc = Document::from_str(yaml).unwrap();
1329 let mapping = doc.as_mapping().unwrap();
1330
1331 let empty1 = mapping.get("empty1").unwrap();
1332 let empty2 = mapping.get("empty2").unwrap();
1333
1334 assert!(yaml_eq(&empty1, &empty2));
1335 assert!(yaml_eq(&empty1, &""));
1336 }
1337
1338 #[test]
1339 fn test_yaml_eq_whitespace_handling() {
1340 let yaml = r#"
1341spaces1: " leading"
1342spaces2: "trailing "
1343spaces3: " both "
1344plain: value
1345"#;
1346
1347 let doc = Document::from_str(yaml).unwrap();
1348 let mapping = doc.as_mapping().unwrap();
1349
1350 let spaces1 = mapping.get("spaces1").unwrap();
1351 let spaces2 = mapping.get("spaces2").unwrap();
1352 let spaces3 = mapping.get("spaces3").unwrap();
1353 let plain = mapping.get("plain").unwrap();
1354
1355 assert!(yaml_eq(&spaces1, &" leading"));
1357 assert!(yaml_eq(&spaces2, &"trailing "));
1358 assert!(yaml_eq(&spaces3, &" both "));
1359 assert!(yaml_eq(&plain, &"value"));
1360
1361 assert!(!yaml_eq(&spaces1, &"leading"));
1363 assert!(!yaml_eq(&spaces2, &"trailing"));
1364 }
1365
1366 #[test]
1367 fn test_yaml_eq_different_types() {
1368 let yaml = r#"
1369string: "123"
1370number: 123
1371sequence:
1372 - item
1373mapping:
1374 key: value
1375"#;
1376
1377 let doc = Document::from_str(yaml).unwrap();
1378 let mapping = doc.as_mapping().unwrap();
1379
1380 let string_val = mapping.get("string").unwrap();
1381 let number_val = mapping.get("number").unwrap();
1382 let sequence_val = mapping.get("sequence").unwrap();
1383 let mapping_val = mapping.get("mapping").unwrap();
1384
1385 assert!(!yaml_eq(&string_val, &number_val));
1388 assert!(!yaml_eq(&string_val, &sequence_val));
1389 assert!(!yaml_eq(&string_val, &mapping_val));
1390 assert!(!yaml_eq(&number_val, &sequence_val));
1391 assert!(!yaml_eq(&sequence_val, &mapping_val));
1392 }
1393
1394 #[test]
1395 fn test_yaml_eq_line_folding_escapes() {
1396 let yaml = r#"
1397folded1: "line1\
1398 line2"
1399folded2: "line1 line2"
1400"#;
1401
1402 let doc = Document::from_str(yaml).unwrap();
1403 let mapping = doc.as_mapping().unwrap();
1404
1405 let folded1 = mapping.get("folded1").unwrap();
1406 let folded2 = mapping.get("folded2").unwrap();
1407
1408 assert!(yaml_eq(&folded1, &folded2));
1410 assert!(yaml_eq(&folded1, &"line1 line2"));
1411 }
1412
1413 #[test]
1414 fn test_yaml_eq_complex_unicode() {
1415 let yaml = r#"
1416emoji1: "😀"
1417emoji2: "\U0001F600"
1418"#;
1419
1420 let doc = Document::from_str(yaml).unwrap();
1421 let mapping = doc.as_mapping().unwrap();
1422
1423 let emoji1 = mapping.get("emoji1").unwrap();
1424 let emoji2 = mapping.get("emoji2").unwrap();
1425
1426 assert!(yaml_eq(&emoji1, &emoji2));
1428 assert!(yaml_eq(&emoji1, &"😀"));
1429 }
1430
1431 #[test]
1432 fn test_yaml_eq_block_scalars() {
1433 let yaml1 = "literal1: |\n line1\n line2\n";
1434 let yaml2 = "literal2: |\n line1\n line2\n";
1435
1436 let doc1 = Document::from_str(yaml1).unwrap();
1437 let doc2 = Document::from_str(yaml2).unwrap();
1438
1439 let mapping1 = doc1.as_mapping().unwrap();
1440 let mapping2 = doc2.as_mapping().unwrap();
1441
1442 let literal1 = mapping1.get("literal1").unwrap();
1443 let literal2 = mapping2.get("literal2").unwrap();
1444
1445 assert!(yaml_eq(&literal1, &literal2));
1447 }
1448
1449 #[test]
1450 fn test_yaml_eq_null_and_special_values() {
1451 let yaml = r#"
1452null1: null
1453null2: null
1454null3:
1455empty: ""
1456"#;
1457
1458 let doc = Document::from_str(yaml).unwrap();
1459 let mapping = doc.as_mapping().unwrap();
1460
1461 let null1 = mapping.get("null1").unwrap();
1462 let null2 = mapping.get("null2").unwrap();
1463 let null3 = mapping.get("null3").unwrap();
1464 let empty = mapping.get("empty").unwrap();
1465
1466 assert!(yaml_eq(&null1, &null2));
1468
1469 assert!(yaml_eq(&null3, &null1));
1472 assert!(yaml_eq(&null3, &null2));
1473
1474 assert!(!yaml_eq(&null1, &empty));
1476
1477 assert!(!yaml_eq(&null1, &"null"));
1479 }
1480
1481 #[test]
1482 fn test_yaml_eq_boolean_representations() {
1483 let yaml = r#"
1484true1: true
1485true2: True
1486true3: TRUE
1487false1: false
1488false2: False
1489"#;
1490
1491 let doc = Document::from_str(yaml).unwrap();
1492 let mapping = doc.as_mapping().unwrap();
1493
1494 let true1 = mapping.get("true1").unwrap();
1495 let true2 = mapping.get("true2").unwrap();
1496 let true3 = mapping.get("true3").unwrap();
1497 let false1 = mapping.get("false1").unwrap();
1498 let false2 = mapping.get("false2").unwrap();
1499
1500 assert!(yaml_eq(&true1, &true2));
1502 assert!(yaml_eq(&true1, &true3));
1503 assert!(yaml_eq(&false1, &false2));
1504
1505 assert!(!yaml_eq(&true1, &"true"));
1507 assert!(!yaml_eq(&false1, &"false"));
1508 }
1509
1510 #[test]
1511 fn test_yaml_eq_numeric_formats() {
1512 let yaml = r#"
1513decimal: 123
1514octal: 0o173
1515hex: 0x7B
1516"#;
1517
1518 let doc = Document::from_str(yaml).unwrap();
1519 let mapping = doc.as_mapping().unwrap();
1520
1521 let decimal = mapping.get("decimal").unwrap();
1522 let octal = mapping.get("octal").unwrap();
1523 let hex = mapping.get("hex").unwrap();
1524
1525 assert!(yaml_eq(&decimal, &octal));
1527 assert!(yaml_eq(&decimal, &hex));
1528 assert!(yaml_eq(&octal, &hex));
1529
1530 assert!(!yaml_eq(&decimal, &"123"));
1532
1533 assert!(yaml_eq(&decimal, &123));
1535 assert!(yaml_eq(&octal, &123));
1536 assert!(yaml_eq(&hex, &123));
1537 }
1538
1539 #[test]
1540 fn test_yaml_eq_with_anchors() {
1541 let yaml = r#"
1542original: &anchor value
1543duplicate: value
1544"#;
1545
1546 let doc = Document::from_str(yaml).unwrap();
1547 let mapping = doc.as_mapping().unwrap();
1548
1549 let original = mapping.get("original").unwrap();
1550 let duplicate = mapping.get("duplicate").unwrap();
1551
1552 assert!(yaml_eq(&original, &duplicate));
1554 assert!(yaml_eq(&original, &"value"));
1555 }
1556
1557 #[test]
1558 fn test_yaml_eq_flow_vs_block_collections() {
1559 let yaml = r#"
1560flow_seq: [1, 2, 3]
1561block_seq:
1562 - 1
1563 - 2
1564 - 3
1565flow_map: {a: 1, b: 2}
1566block_map:
1567 a: 1
1568 b: 2
1569"#;
1570
1571 let doc = Document::from_str(yaml).unwrap();
1572 let mapping = doc.as_mapping().unwrap();
1573
1574 let flow_seq = mapping.get("flow_seq").unwrap();
1575 let block_seq = mapping.get("block_seq").unwrap();
1576 let flow_map = mapping.get("flow_map").unwrap();
1577 let block_map = mapping.get("block_map").unwrap();
1578
1579 assert!(yaml_eq(&flow_seq, &block_seq));
1581 assert!(yaml_eq(&flow_map, &block_map));
1582 }
1583
1584 #[test]
1585 fn test_option_none_emits_null() {
1586 let doc = Document::from_str("key: value\n").unwrap();
1587 doc.as_mapping().unwrap().set("key", None::<&str>);
1588 assert_eq!(doc.to_string(), "key: null\n");
1589 }
1590
1591 #[test]
1592 fn test_option_some_delegates() {
1593 let doc = Document::from_str("key: value\n").unwrap();
1594 doc.as_mapping().unwrap().set("key", Some("hello"));
1595 assert_eq!(doc.to_string(), "key: hello\n");
1596
1597 let doc = Document::from_str("key: value\n").unwrap();
1598 doc.as_mapping().unwrap().set("key", Some(42i64));
1599 assert_eq!(doc.to_string(), "key: 42\n");
1600 }
1601}