1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fmt;
4
5use crate::syntax::node_link;
6use crate::{
7 Children, CollectionStyle, Diagnostic, DiagnosticKind, FromYamlDoc, Node, NodeId, NodeKind,
8 Parser, ScalarStyle, SemanticKind, SemanticProperties, SemanticStore, Source, Span, ToYamlDoc,
9 ToYamlFragment, Token, YamlEditError, YamlError, YamlEvent, YamlFragment,
10 decode_scalar_value_with_content_indent, directive_emit_error, double_quoted_scalar_end,
11 edits_conflict, events_to_test_string, format_scalar_value, lex, parse_node_properties,
12 plain_scalar_end, resolve_tag, single_quoted_scalar_end, strip_inline_comment,
13 validate_plain_mapping_fragment, validate_tag_directive_parts_for_emit, validate_yaml_chars,
14 validate_yaml_directive_version_for_emit,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Edit {
20 pub span: Span,
22 pub replacement: String,
24}
25
26pub struct YamlEvents<'doc> {
28 doc: &'doc YamlDoc,
29 tasks: Vec<EventTask>,
30}
31
32impl Iterator for YamlEvents<'_> {
33 type Item = YamlEvent;
34
35 fn next(&mut self) -> Option<Self::Item> {
36 while let Some(task) = self.tasks.pop() {
37 match task {
38 EventTask::StreamStart => {
39 return Some(YamlEvent {
40 kind: crate::YamlEventKind::StreamStart,
41 span: Span::from_usize(0, self.doc.source.len()),
42 cst: None,
43 content_indent: None,
44 });
45 }
46 EventTask::Documents(index) => self.schedule_document(index),
47 EventTask::DocumentStart(document) => {
48 let Some(metadata) = self.doc.semantic_metadata(document) else {
49 continue;
50 };
51 return Some(YamlEvent {
52 kind: crate::YamlEventKind::DocumentStart {
53 explicit: metadata.explicit_start(),
54 },
55 span: self.doc.semantic_span(document, metadata),
56 cst: Some(document),
57 content_indent: None,
58 });
59 }
60 EventTask::DocumentChildren(next) => self.schedule_document_child(next),
61 EventTask::DocumentEnd(document) => {
62 let Some(metadata) = self.doc.semantic_metadata(document) else {
63 continue;
64 };
65 return Some(YamlEvent {
66 kind: crate::YamlEventKind::DocumentEnd {
67 explicit: metadata.explicit_end(),
68 },
69 span: self.doc.semantic_end_span(document, metadata),
70 cst: None,
71 content_indent: None,
72 });
73 }
74 EventTask::Node(node) => {
75 if let Some(event) = self.schedule_node(node) {
76 return Some(event);
77 }
78 }
79 EventTask::MappingEntries(next) => self.schedule_mapping_entry(next),
80 EventTask::SequenceEntries(next) => self.schedule_sequence_entry(next),
81 EventTask::CollectionEnd { node, mapping } => {
82 let Some(metadata) = self.doc.semantic_metadata(node) else {
83 continue;
84 };
85 return Some(YamlEvent {
86 kind: if mapping {
87 crate::YamlEventKind::MappingEnd
88 } else {
89 crate::YamlEventKind::SequenceEnd
90 },
91 span: self.doc.semantic_end_span(node, metadata),
92 cst: None,
93 content_indent: None,
94 });
95 }
96 EventTask::StreamEnd => {
97 return Some(YamlEvent {
98 kind: crate::YamlEventKind::StreamEnd,
99 span: Span::empty_from_usize(self.doc.source.len()),
100 cst: None,
101 content_indent: None,
102 });
103 }
104 }
105 }
106 None
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111enum EventTask {
112 StreamStart,
113 Documents(usize),
114 DocumentStart(NodeId),
115 DocumentChildren(u32),
116 DocumentEnd(NodeId),
117 Node(NodeId),
118 MappingEntries(u32),
119 SequenceEntries(u32),
120 CollectionEnd { node: NodeId, mapping: bool },
121 StreamEnd,
122}
123
124impl YamlEvents<'_> {
125 fn schedule_document(&mut self, index: usize) {
126 let Some(&document) = self.doc.semantics.documents.get(index) else {
127 return;
128 };
129 let Some(node) = self.doc.node(document) else {
130 self.tasks.push(EventTask::Documents(index + 1));
131 return;
132 };
133 self.tasks.push(EventTask::Documents(index + 1));
134 self.tasks.push(EventTask::DocumentEnd(document));
135 self.tasks
136 .push(EventTask::DocumentChildren(node.first_child));
137 self.tasks.push(EventTask::DocumentStart(document));
138 }
139
140 fn schedule_document_child(&mut self, next: u32) {
141 let Some(child) = node_link(next) else {
142 return;
143 };
144 self.tasks.push(EventTask::DocumentChildren(
145 self.doc.nodes[child.as_usize()].next_sibling,
146 ));
147 if self.doc.semantic_metadata(child).is_some() {
148 self.tasks.push(EventTask::Node(child));
149 }
150 }
151
152 fn schedule_node(&mut self, node: NodeId) -> Option<YamlEvent> {
153 let metadata = self.doc.semantic_metadata(node)?;
154 let span = self.doc.semantic_span(node, metadata);
155 match metadata.kind {
156 SemanticKind::Document => None,
157 SemanticKind::Mapping { style } => {
158 self.tasks.push(EventTask::CollectionEnd {
159 node,
160 mapping: true,
161 });
162 self.tasks.push(EventTask::MappingEntries(
163 self.doc.nodes[node.as_usize()].first_child,
164 ));
165 Some(YamlEvent {
166 kind: crate::YamlEventKind::MappingStart {
167 style,
168 tag: self
169 .doc
170 .resolved_tag(node)
171 .ok()
172 .flatten()
173 .map(Cow::into_owned),
174 anchor: self.doc.anchor(node).map(str::to_owned),
175 },
176 span,
177 cst: Some(node),
178 content_indent: None,
179 })
180 }
181 SemanticKind::Sequence { style } => {
182 self.tasks.push(EventTask::CollectionEnd {
183 node,
184 mapping: false,
185 });
186 self.tasks.push(EventTask::SequenceEntries(
187 self.doc.nodes[node.as_usize()].first_child,
188 ));
189 Some(YamlEvent {
190 kind: crate::YamlEventKind::SequenceStart {
191 style,
192 tag: self
193 .doc
194 .resolved_tag(node)
195 .ok()
196 .flatten()
197 .map(Cow::into_owned),
198 anchor: self.doc.anchor(node).map(str::to_owned),
199 },
200 span,
201 cst: Some(node),
202 content_indent: None,
203 })
204 }
205 SemanticKind::Scalar { style } => Some(YamlEvent {
206 kind: crate::YamlEventKind::Scalar {
207 style,
208 value: self
209 .doc
210 .scalar_value(node)
211 .map(Cow::into_owned)
212 .unwrap_or_default(),
213 tag: self
214 .doc
215 .resolved_tag(node)
216 .ok()
217 .flatten()
218 .map(Cow::into_owned),
219 anchor: self.doc.anchor(node).map(str::to_owned),
220 },
221 span,
222 cst: Some(node),
223 content_indent: self
224 .doc
225 .semantic_properties(node)
226 .and_then(|properties| properties.content_indent),
227 }),
228 SemanticKind::Alias => Some(YamlEvent {
229 kind: crate::YamlEventKind::Alias {
230 name: self.doc.alias_name(node).unwrap_or_default().to_owned(),
231 },
232 span,
233 cst: Some(node),
234 content_indent: None,
235 }),
236 }
237 }
238
239 fn schedule_mapping_entry(&mut self, next: u32) {
240 let Some(entry) = node_link(next) else {
241 return;
242 };
243 self.tasks.push(EventTask::MappingEntries(
244 self.doc.nodes[entry.as_usize()].next_sibling,
245 ));
246 if self.doc.nodes[entry.as_usize()].kind != NodeKind::MappingEntry {
247 return;
248 }
249 let Some(key) = self.first_semantic_child(entry) else {
250 return;
251 };
252 let Some(value) = self.next_semantic_sibling(key) else {
253 return;
254 };
255 self.tasks.push(EventTask::Node(value));
256 self.tasks.push(EventTask::Node(key));
257 }
258
259 fn schedule_sequence_entry(&mut self, next: u32) {
260 let Some(entry) = node_link(next) else {
261 return;
262 };
263 self.tasks.push(EventTask::SequenceEntries(
264 self.doc.nodes[entry.as_usize()].next_sibling,
265 ));
266 if self.doc.nodes[entry.as_usize()].kind != NodeKind::SequenceEntry {
267 return;
268 }
269 if let Some(item) = self.first_semantic_child(entry) {
270 self.tasks.push(EventTask::Node(item));
271 }
272 }
273
274 fn first_semantic_child(&self, parent: NodeId) -> Option<NodeId> {
275 let next = self.doc.nodes[parent.as_usize()].first_child;
276 self.next_semantic(next)
277 }
278
279 fn next_semantic_sibling(&self, node: NodeId) -> Option<NodeId> {
280 let next = self.doc.nodes[node.as_usize()].next_sibling;
281 self.next_semantic(next)
282 }
283
284 fn next_semantic(&self, mut next: u32) -> Option<NodeId> {
285 while let Some(node) = node_link(next) {
286 if self.doc.semantic_metadata(node).is_some() {
287 return Some(node);
288 }
289 next = self.doc.nodes[node.as_usize()].next_sibling;
290 }
291 None
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct YamlDirective {
298 pub version: String,
300 pub node: NodeId,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct TagDirective {
307 pub handle: String,
309 pub prefix: String,
311 pub node: NodeId,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
317pub struct ReservedDirective {
318 pub name: String,
320 pub parameters: Vec<String>,
322 pub node: NodeId,
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum ParsedDirective {
328 Yaml(YamlDirective),
329 Tag(TagDirective),
330 Reserved(ReservedDirective),
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
335pub enum MappingEntryStyle {
336 #[default]
338 Inherit,
339 Indent(usize),
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct YamlDoc {
346 pub(crate) source: Source,
348 pub(crate) nodes: Vec<Node>,
350 pub(crate) semantics: SemanticStore,
352 pub(crate) root_override: Option<NodeId>,
354 pub(crate) edits: Vec<Edit>,
356}
357
358impl YamlDoc {
359 pub fn parse(input: &str) -> Result<Self, YamlError> {
369 Self::parse_owned(input.to_owned())
370 }
371
372 pub fn parse_owned(input: String) -> Result<Self, YamlError> {
379 let source = Source::new(input)?;
380 let parsed = Parser::new(&source)
381 .parse()
382 .map_err(|error| error.with_position_from(&source))?;
383 Ok(Self {
384 source,
385 nodes: parsed.nodes,
386 semantics: parsed.semantics,
387 root_override: None,
388 edits: Vec::new(),
389 })
390 }
391
392 pub fn commit_edits(&mut self) -> Result<(), YamlError> {
404 if self.edits.is_empty() {
405 return Ok(());
406 }
407
408 let edited = self.to_string();
409 *self = Self::parse(&edited)?;
410 Ok(())
411 }
412
413 #[must_use]
415 pub fn as_source(&self) -> &str {
416 self.source.as_str()
417 }
418
419 #[must_use]
421 pub const fn source(&self) -> &Source {
422 &self.source
423 }
424
425 pub fn tokens(&self) -> Result<Vec<Token>, YamlError> {
434 lex(&self.source).map_err(|error| error.with_position_from(&self.source))
435 }
436
437 #[must_use]
439 pub fn root(&self) -> Option<NodeId> {
440 (!self.nodes.is_empty()).then_some(NodeId(0))
441 }
442
443 #[must_use]
445 pub fn events(&self) -> YamlEvents<'_> {
446 let mut tasks = Vec::with_capacity(8);
447 tasks.push(EventTask::StreamEnd);
448 tasks.push(EventTask::Documents(0));
449 tasks.push(EventTask::StreamStart);
450 YamlEvents { doc: self, tasks }
451 }
452
453 #[must_use]
455 pub fn events_to_test_string(&self) -> String {
456 events_to_test_string(self.events())
457 }
458
459 #[must_use]
461 pub fn document_count(&self) -> usize {
462 self.root_override
463 .map_or(self.semantics.documents.len(), |_| 1)
464 }
465
466 pub fn append_document<T>(&mut self, value: &T) -> Result<(), YamlError>
476 where
477 T: ToYamlFragment,
478 {
479 let line_ending = self.preferred_line_ending();
480 let mut replacement = self.document_append_prefix(line_ending);
481 replacement.push_str("---");
482 replacement.push_str(line_ending);
483 let fragment = value.to_yaml_fragment(0, line_ending)?;
484 replacement.push_str(&fragment);
485 replacement.push_str(line_ending);
486 self.queue_edit(Span::empty_from_usize(self.source.len()), replacement)
487 }
488
489 pub fn append_empty_mapping_document(&mut self) -> Result<(), YamlError> {
496 self.append_document(&std::collections::BTreeMap::<String, String>::new())
497 }
498
499 #[must_use]
501 pub fn yaml_directive(&self) -> Option<YamlDirective> {
502 self.directive_nodes()
503 .filter_map(|node| self.parse_directive_node(node).ok())
504 .find_map(|directive| match directive {
505 ParsedDirective::Yaml(directive) => Some(directive),
506 ParsedDirective::Tag(_) | ParsedDirective::Reserved(_) => None,
507 })
508 }
509
510 #[must_use]
512 pub fn tag_directives(&self) -> Vec<TagDirective> {
513 self.directive_nodes()
514 .filter_map(|node| self.parse_directive_node(node).ok())
515 .filter_map(|directive| match directive {
516 ParsedDirective::Tag(directive) => Some(directive),
517 ParsedDirective::Yaml(_) | ParsedDirective::Reserved(_) => None,
518 })
519 .collect()
520 }
521
522 #[must_use]
524 pub fn reserved_directives(&self) -> Vec<ReservedDirective> {
525 self.directive_nodes()
526 .filter_map(|node| self.parse_directive_node(node).ok())
527 .filter_map(|directive| match directive {
528 ParsedDirective::Reserved(directive) => Some(directive),
529 ParsedDirective::Yaml(_) | ParsedDirective::Tag(_) => None,
530 })
531 .collect()
532 }
533
534 pub fn set_yaml_directive(&mut self, version: &str) -> Result<(), YamlError> {
541 validate_yaml_directive_version_for_emit(version)?;
542 let replacement = format!("%YAML {version}");
543 if let Some(directive) = self.yaml_directive() {
544 let span = self.directive_content_span(directive.node)?;
545 self.queue_edit(span, replacement)
546 } else {
547 self.insert_directive_line(replacement)
548 }
549 }
550
551 pub fn set_tag_directive(&mut self, handle: &str, prefix: &str) -> Result<(), YamlError> {
558 validate_tag_directive_parts_for_emit(handle, prefix)?;
559 let replacement = format!("%TAG {handle} {prefix}");
560 if let Some(directive) = self
561 .tag_directives()
562 .into_iter()
563 .find(|directive| directive.handle == handle)
564 {
565 let span = self.directive_content_span(directive.node)?;
566 self.queue_edit(span, replacement)
567 } else {
568 self.insert_directive_line(replacement)
569 }
570 }
571
572 pub fn remove_yaml_directive(&mut self) -> Result<(), YamlError> {
578 if let Some(directive) = self.yaml_directive() {
579 self.remove_directive_node(directive.node)?;
580 }
581 Ok(())
582 }
583
584 pub fn remove_tag_directive(&mut self, handle: &str) -> Result<(), YamlError> {
590 if let Some(directive) = self
591 .tag_directives()
592 .into_iter()
593 .find(|directive| directive.handle == handle)
594 {
595 self.remove_directive_node(directive.node)?;
596 }
597 Ok(())
598 }
599
600 #[must_use]
602 pub fn node(&self, node: NodeId) -> Option<&Node> {
603 self.nodes.get(node.0 as usize)
604 }
605
606 #[must_use]
608 pub fn children(&self, node: NodeId) -> Children<'_> {
609 Children::new(&self.nodes, node)
610 }
611
612 fn semantic_children(&self, node: NodeId) -> impl Iterator<Item = NodeId> + '_ {
613 self.children(node)
614 .filter(|child| self.semantic_metadata(*child).is_some())
615 }
616
617 #[must_use]
619 pub fn semantic_kind(&self, node: NodeId) -> Option<SemanticKind> {
620 self.semantic_metadata(node).map(|node| node.kind)
621 }
622
623 pub(crate) fn semantic_metadata(&self, node: NodeId) -> Option<crate::semantic::SemanticNode> {
624 self.semantics.get(self.node(node)?)
625 }
626
627 pub(crate) fn semantic_properties(&self, node: NodeId) -> Option<SemanticProperties> {
628 self.semantics.properties(self.node(node)?)
629 }
630
631 #[must_use]
633 pub fn raw_tag(&self, node: NodeId) -> Option<&str> {
634 let span = self.semantic_properties(node)?.tag?;
635 Some(self.source.slice(span))
636 }
637
638 pub fn resolved_tag(&self, node: NodeId) -> Result<Option<Cow<'_, str>>, YamlError> {
644 let Some(raw) = self.raw_tag(node) else {
645 return Ok(None);
646 };
647 let document = self
648 .node(node)
649 .and_then(|cst| self.semantics.property_document(cst))
650 .unwrap_or(node);
651 let handles = self
652 .semantics
653 .tag_directives(document)
654 .map(|(handle, prefix)| {
655 (
656 self.source.slice(handle).to_owned(),
657 self.source.slice(prefix).to_owned(),
658 )
659 })
660 .collect::<BTreeMap<_, _>>();
661 let span = self
662 .semantic_properties(node)
663 .and_then(|properties| properties.tag)
664 .unwrap_or_else(|| self.node(node).map_or(Span::empty(0), Node::span));
665 resolve_tag(raw, &handles, span).map(|tag| Some(Cow::Owned(tag)))
666 }
667
668 #[must_use]
670 pub fn anchor(&self, node: NodeId) -> Option<&str> {
671 let span = self.semantic_properties(node)?.anchor?;
672 Some(self.source.slice(span))
673 }
674
675 #[must_use]
677 pub fn alias_name(&self, node: NodeId) -> Option<&str> {
678 let span = self.semantic_properties(node)?.alias?;
679 Some(self.source.slice(span))
680 }
681
682 #[must_use]
684 pub fn resolve_alias(&self, node: NodeId) -> Option<NodeId> {
685 let name = self.alias_name(node)?;
686 let document = self.semantics.property_document(self.node(node)?)?;
687 let alias_start = self.node(node)?.span.start;
688 self.semantics
689 .anchors()
690 .rev()
691 .find(|(span, target, anchor_document)| {
692 *anchor_document == document
693 && self
694 .node(*target)
695 .is_some_and(|node| node.span.start <= alias_start)
696 && self.source.slice(*span) == name
697 })
698 .map(|(_, target, _)| target)
699 }
700
701 fn semantic_span(&self, node: NodeId, metadata: crate::semantic::SemanticNode) -> Span {
702 let cst_start = self
703 .node(node)
704 .map_or(metadata.end_offset, |node| node.span.start);
705 Span::new(
706 self.semantics
707 .span_start(self.node(node).expect("semantic node has CST"), cst_start),
708 self.node(node)
709 .map_or(metadata.end_offset, |node| node.span.end),
710 )
711 }
712
713 fn semantic_end_span(&self, node: NodeId, metadata: crate::semantic::SemanticNode) -> Span {
714 if metadata.explicit_end()
715 && let Some(marker) = self.children(node).find_map(|child| {
716 let child = self.node(child)?;
717 (child.kind == NodeKind::DocumentMarker
718 && self.source.slice(child.span).starts_with("..."))
719 .then_some(child.span)
720 })
721 {
722 return marker;
723 }
724 if matches!(
725 metadata.kind,
726 SemanticKind::Mapping {
727 style: CollectionStyle::Flow
728 } | SemanticKind::Sequence {
729 style: CollectionStyle::Flow
730 }
731 ) {
732 return self.semantic_span(node, metadata);
733 }
734 Span::empty(metadata.end_offset)
735 }
736
737 pub fn documents(&self) -> impl Iterator<Item = NodeId> + '_ {
739 self.semantics.documents.iter().copied()
740 }
741
742 pub fn document_root(&self, index: usize) -> Result<Option<NodeId>, YamlError> {
752 if let Some(root) = self.root_override {
753 return (index == 0)
754 .then_some(Some(root))
755 .ok_or_else(|| self.document_index_error(index));
756 }
757 let document = self
758 .semantics
759 .documents
760 .get(index)
761 .copied()
762 .ok_or_else(|| self.document_index_error(index))?;
763 let root = self.semantic_children(document).next();
764 Ok(root.filter(|root| {
765 !matches!(self.semantic_kind(*root), Some(SemanticKind::Scalar { .. }))
766 || self.node(*root).is_some_and(|node| !node.span.is_empty())
767 }))
768 }
769
770 pub fn mapping_entries(&self, mapping: NodeId) -> impl Iterator<Item = (NodeId, NodeId)> + '_ {
772 let is_mapping = matches!(
773 self.semantic_kind(mapping),
774 Some(SemanticKind::Mapping { .. })
775 );
776 self.children(mapping).filter_map(move |entry| {
777 if !is_mapping || self.node(entry)?.kind != NodeKind::MappingEntry {
778 return None;
779 }
780 let mut children = self.semantic_children(entry);
781 Some((children.next()?, children.next()?))
782 })
783 }
784
785 pub fn sequence_items(&self, sequence: NodeId) -> impl Iterator<Item = NodeId> + '_ {
787 let is_sequence = matches!(
788 self.semantic_kind(sequence),
789 Some(SemanticKind::Sequence { .. })
790 );
791 self.children(sequence).filter_map(move |entry| {
792 if !is_sequence || self.node(entry)?.kind != NodeKind::SequenceEntry {
793 return None;
794 }
795 self.semantic_children(entry).next()
796 })
797 }
798
799 pub fn root_mapping(&self) -> Result<NodeId, YamlError> {
806 self.document_root_mapping(0)
807 }
808
809 pub fn document_root_mapping(&self, index: usize) -> Result<NodeId, YamlError> {
816 if let Some(root) = self.root_override {
817 return (index == 0)
818 .then_some(root)
819 .ok_or_else(|| self.document_index_error(index));
820 }
821 let document = self
822 .semantics
823 .documents
824 .get(index)
825 .copied()
826 .ok_or_else(|| self.document_index_error(index))?;
827 self.semantic_children(document)
828 .find(|child| {
829 self.node(*child).is_some_and(|node| {
830 matches!(node.kind, NodeKind::BlockMapping | NodeKind::FlowMapping)
831 }) && matches!(
832 self.semantic_kind(*child),
833 Some(SemanticKind::Mapping { .. })
834 )
835 })
836 .ok_or_else(|| {
837 YamlError::new(
838 Diagnostic::new(
839 DiagnosticKind::Semantic,
840 "document does not contain a root mapping",
841 self.node(document).map_or(Span::empty(0), |node| node.span),
842 )
843 .with_expected("a block or flow mapping node"),
844 )
845 })
846 }
847
848 pub fn read_document<T>(&self, index: usize) -> Result<T, YamlError>
855 where
856 T: FromYamlDoc,
857 {
858 let root = self
859 .document_root(index)?
860 .ok_or_else(|| self.empty_document_error(index))?;
861 let nested = self.rerooted_at(root)?;
862 T::from_yaml_doc(&nested)
863 }
864
865 pub fn write_document<T>(&mut self, index: usize, value: &T) -> Result<(), YamlError>
872 where
873 T: ToYamlDoc,
874 {
875 let root = self
876 .document_root(index)?
877 .ok_or_else(|| self.empty_document_error(index))?;
878 let mut nested = self.rerooted_at(root)?;
879 value.apply_to_yaml_doc(&mut nested)?;
880 self.queue_edits_from(&nested)
881 }
882
883 pub fn get_mapping_entry(
889 &self,
890 mapping: NodeId,
891 key: &str,
892 ) -> Result<Option<NodeId>, YamlError> {
893 Ok(self
894 .find_mapping_pair(mapping, key)?
895 .and_then(|(key, _)| self.containing_entry(key)))
896 }
897
898 pub fn get_mapping_value(
904 &self,
905 mapping: NodeId,
906 key: &str,
907 ) -> Result<Option<NodeId>, YamlError> {
908 Ok(self
909 .find_mapping_pair(mapping, key)?
910 .map(|(_, value)| value))
911 }
912
913 pub fn get_path(&self, path: &[&str]) -> Result<Option<NodeId>, YamlError> {
919 self.get_path_in_document(0, path)
920 }
921
922 pub fn get_path_in_document(
929 &self,
930 index: usize,
931 path: &[&str],
932 ) -> Result<Option<NodeId>, YamlError> {
933 let Some((first, rest)) = path.split_first() else {
934 return Ok(None);
935 };
936 let Some((_, mut current)) =
937 self.find_mapping_pair(self.document_root_mapping(index)?, first)?
938 else {
939 return Ok(None);
940 };
941 for segment in rest {
942 let Some((_, value)) = self.find_mapping_pair(current, segment)? else {
943 return Ok(None);
944 };
945 current = value;
946 }
947 Ok(Some(current))
948 }
949
950 fn document_index_error(&self, index: usize) -> YamlError {
951 YamlError::new(
952 Diagnostic::new(
953 DiagnosticKind::Semantic,
954 format!("document index {index} is out of range"),
955 Span::empty_from_usize(self.source.len()),
956 )
957 .with_expected("an existing document index"),
958 )
959 }
960
961 fn empty_document_error(&self, index: usize) -> YamlError {
962 YamlError::new(
963 Diagnostic::new(
964 DiagnosticKind::Typed,
965 format!("document {index} does not contain a YAML value"),
966 Span::empty_from_usize(self.source.len()),
967 )
968 .with_expected("a scalar, sequence, or mapping document root"),
969 )
970 }
971
972 fn find_mapping_pair(
973 &self,
974 mapping: NodeId,
975 key: &str,
976 ) -> Result<Option<(NodeId, NodeId)>, YamlError> {
977 for (key_node, value_node) in self.mapping_entries(mapping) {
978 if self.scalar_value(key_node)? == key {
979 return Ok(Some((key_node, value_node)));
980 }
981 }
982 Ok(None)
983 }
984
985 pub(crate) fn rerooted_at(&self, root: NodeId) -> Result<Self, YamlError> {
986 let root_node = self.expect_node(root)?;
987 if self.semantic_kind(root).is_none() {
988 return Err(YamlError::new(
989 Diagnostic::new(
990 DiagnosticKind::Semantic,
991 "typed overlay root does not have semantic metadata",
992 root_node.span,
993 )
994 .with_expected("a semantic YAML value"),
995 )
996 .with_position_from(&self.source));
997 }
998 let mut doc = self.clone();
999 doc.root_override = Some(root);
1000 doc.edits.clear();
1001 Ok(doc)
1002 }
1003
1004 pub(crate) fn rerooted_without_tag(&self, root: NodeId) -> Result<Self, YamlError> {
1005 let mut doc = self.rerooted_at(root)?;
1006 doc.semantics.clear_tag(&doc.nodes[root.as_usize()]);
1007 Ok(doc)
1008 }
1009
1010 pub(crate) fn rerooted_at_mapping(&self, mapping: NodeId) -> Result<Self, YamlError> {
1011 let mapping_node = self.expect_node(mapping)?;
1012 if !matches!(
1013 mapping_node.kind,
1014 NodeKind::BlockMapping | NodeKind::FlowMapping
1015 ) {
1016 return Err(YamlError::new(
1017 Diagnostic::new(
1018 DiagnosticKind::Semantic,
1019 format!("expected mapping, found {:?}", mapping_node.kind),
1020 mapping_node.span,
1021 )
1022 .with_expected("BlockMapping or FlowMapping"),
1023 )
1024 .with_position_from(&self.source));
1025 }
1026 if !matches!(
1027 self.semantic_kind(mapping),
1028 Some(SemanticKind::Mapping { .. })
1029 ) {
1030 return Err(YamlError::new(
1031 Diagnostic::new(
1032 DiagnosticKind::Semantic,
1033 "mapping does not have semantic metadata",
1034 self.expect_node(mapping)?.span,
1035 )
1036 .with_expected("a semantic mapping"),
1037 )
1038 .with_position_from(&self.source));
1039 }
1040 self.rerooted_at(mapping)
1041 }
1042
1043 pub(crate) fn queue_edits_from(&mut self, other: &YamlDoc) -> Result<(), YamlError> {
1044 for edit in &other.edits {
1045 self.queue_edit(edit.span, edit.replacement.clone())?;
1046 }
1047 Ok(())
1048 }
1049
1050 pub fn scalar_text(&self, node: NodeId) -> Result<&str, YamlError> {
1057 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1058 Ok(self.source.slice(node.span))
1059 }
1060
1061 pub fn scalar_value(&self, node: NodeId) -> Result<Cow<'_, str>, YamlError> {
1072 let node_ref = self.expect_node(node)?;
1073 if !matches!(
1074 node_ref.kind,
1075 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1076 ) {
1077 return Err(YamlError::new(
1078 Diagnostic::new(
1079 DiagnosticKind::Semantic,
1080 format!("expected scalar value, found {:?}", node_ref.kind),
1081 node_ref.span,
1082 )
1083 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1084 )
1085 .with_position_from(&self.source));
1086 }
1087 let text = self.source.slice(node_ref.span);
1088 let properties = parse_node_properties(text, node_ref.span)?;
1089 let value_text = &text[properties.value_start()..];
1090 if matches!(
1091 self.semantic_kind(node),
1092 Some(SemanticKind::Scalar {
1093 style: crate::YamlScalarStyle::Plain,
1094 ..
1095 })
1096 ) && !value_text.contains(['\n', '\r'])
1097 {
1098 return Ok(Cow::Borrowed(&value_text[..plain_scalar_end(value_text)]));
1099 }
1100 decode_scalar_value_with_content_indent(
1101 value_text,
1102 self.semantic_properties(node)
1103 .and_then(|properties| properties.content_indent)
1104 .map(|indent| indent as usize),
1105 )
1106 .map(Cow::Owned)
1107 }
1108
1109 pub fn borrowable_scalar_span(&self, node: NodeId) -> Result<Option<Span>, YamlError> {
1121 let node_ref = self.expect_node(node)?;
1122 if !matches!(
1123 self.semantic_kind(node),
1124 Some(SemanticKind::Scalar {
1125 style: crate::YamlScalarStyle::Plain,
1126 })
1127 ) {
1128 if matches!(
1129 node_ref.kind,
1130 NodeKind::Scalar | NodeKind::LiteralScalar | NodeKind::FoldedScalar
1131 ) {
1132 return Ok(None);
1133 }
1134 return Err(YamlError::new(
1135 Diagnostic::new(
1136 DiagnosticKind::Semantic,
1137 format!("expected scalar value, found {:?}", node_ref.kind),
1138 node_ref.span,
1139 )
1140 .with_expected("Scalar, LiteralScalar, or FoldedScalar"),
1141 )
1142 .with_position_from(&self.source));
1143 }
1144
1145 let text = self.source.slice(node_ref.span);
1146 if text.contains(['\n', '\r']) {
1147 return Ok(None);
1148 }
1149 let properties = parse_node_properties(text, node_ref.span)?;
1150 let value_text = &text[properties.value_start()..];
1151 let value_len = plain_scalar_end(value_text);
1152 let start = Span::offset_from_usize(node_ref.span.start, properties.value_start());
1153 Ok(Some(Span::new(
1154 start,
1155 Span::offset_from_usize(start, value_len),
1156 )))
1157 }
1158
1159 pub fn set_scalar(&mut self, path: &[&str], value: &str) -> Result<(), YamlError> {
1173 let node = self.get_path(path)?.ok_or_else(|| {
1174 YamlError::new(
1175 Diagnostic::new(
1176 DiagnosticKind::Semantic,
1177 format!("path `{}` does not exist", path.join(".")),
1178 Span::empty(0),
1179 )
1180 .with_expected("an existing scalar node"),
1181 )
1182 })?;
1183
1184 let (span, style) = self.scalar_replacement_target(node)?;
1185 let replacement = format_scalar_value(value, style)?;
1186 self.queue_edit(span, replacement)
1187 }
1188
1189 pub fn replace_node_text(
1199 &mut self,
1200 node: NodeId,
1201 text: impl Into<String>,
1202 ) -> Result<(), YamlError> {
1203 let span = self.expect_node(node)?.span;
1204 self.queue_edit(span, text.into())
1205 }
1206
1207 pub fn insert_mapping_entry(
1218 &mut self,
1219 mapping: NodeId,
1220 key: &str,
1221 value: &str,
1222 style: MappingEntryStyle,
1223 ) -> Result<(), YamlError> {
1224 self.insert_mapping_entry_with_comment(mapping, key, value, style, None)
1225 }
1226
1227 pub fn insert_mapping_entry_with_comment(
1239 &mut self,
1240 mapping: NodeId,
1241 key: &str,
1242 value: &str,
1243 style: MappingEntryStyle,
1244 comment: Option<&str>,
1245 ) -> Result<(), YamlError> {
1246 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1247 let indent = match style {
1248 MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1249 MappingEntryStyle::Indent(indent) => indent,
1250 };
1251 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1252 let needs_leading_break =
1253 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1254 let preserve_paragraph_break = comment.is_some()
1255 && insertion_offset == self.source.len()
1256 && self.mapping_has_blank_line(mapping_node);
1257 let replacement = self.format_mapping_entry_replacement(
1258 indent,
1259 key,
1260 value,
1261 comment,
1262 needs_leading_break,
1263 preserve_paragraph_break,
1264 )?;
1265
1266 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1267 }
1268
1269 pub fn insert_mapping_value_with_comment<T>(
1277 &mut self,
1278 mapping: NodeId,
1279 key: &str,
1280 value: &T,
1281 style: MappingEntryStyle,
1282 comment: Option<&str>,
1283 ) -> Result<(), YamlError>
1284 where
1285 T: ToYamlFragment,
1286 {
1287 if matches!(
1288 self.semantic_kind(mapping),
1289 Some(SemanticKind::Mapping {
1290 style: CollectionStyle::Flow
1291 })
1292 ) {
1293 let fragment = self.typed_value_fragment(value)?;
1294 return self
1295 .queue_mapping_insert(mapping, key, &fragment)
1296 .map_err(YamlEditError::into_yaml_error);
1297 }
1298 let mapping_node = self.expect_node_kind(mapping, NodeKind::BlockMapping)?;
1299 let indent = match style {
1300 MappingEntryStyle::Inherit => self.node_indent(mapping_node),
1301 MappingEntryStyle::Indent(indent) => indent,
1302 };
1303 let insertion_offset = self.mapping_insertion_offset(mapping_node);
1304 let needs_leading_break =
1305 insertion_offset == self.source.len() && !self.source_ends_with_line_break();
1306 let preserve_paragraph_break = comment.is_some()
1307 && insertion_offset == self.source.len()
1308 && self.mapping_has_blank_line(mapping_node);
1309 let replacement = self.format_mapping_value_replacement(
1310 indent,
1311 key,
1312 value,
1313 comment,
1314 needs_leading_break,
1315 preserve_paragraph_break,
1316 )?;
1317
1318 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1319 }
1320
1321 fn typed_value_fragment<T>(&self, value: &T) -> Result<YamlFragment, YamlError>
1322 where
1323 T: ToYamlFragment,
1324 {
1325 let yaml = value.to_yaml_fragment(0, self.preferred_line_ending())?;
1326 YamlFragment::parse(&yaml).map_err(|error| {
1327 YamlError::new(
1328 Diagnostic::new(
1329 DiagnosticKind::Emitter,
1330 format!("typed YAML fragment is invalid: {error}"),
1331 Span::empty(0),
1332 )
1333 .with_expected("one valid YAML value"),
1334 )
1335 })
1336 }
1337
1338 pub fn insert_mapping_value_ordered_with_comment<T>(
1345 &mut self,
1346 mapping: NodeId,
1347 key: &str,
1348 value: &T,
1349 style: MappingEntryStyle,
1350 comment: Option<&str>,
1351 ordered_keys: &[&str],
1352 ) -> Result<(), YamlError>
1353 where
1354 T: ToYamlFragment,
1355 {
1356 let mut next_entry = None;
1357 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1358 for later_key in &ordered_keys[position + 1..] {
1359 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1360 next_entry = Some(entry);
1361 break;
1362 }
1363 }
1364 }
1365
1366 if let Some(next_entry) = next_entry {
1367 self.insert_mapping_value_before_with_comment(next_entry, key, value, style, comment)
1368 } else {
1369 self.insert_mapping_value_with_comment(mapping, key, value, style, comment)
1370 }
1371 }
1372
1373 pub fn insert_mapping_entry_before_with_comment(
1381 &mut self,
1382 before_entry: NodeId,
1383 key: &str,
1384 value: &str,
1385 style: MappingEntryStyle,
1386 comment: Option<&str>,
1387 ) -> Result<(), YamlError> {
1388 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1389 let indent = match style {
1390 MappingEntryStyle::Inherit => self.node_indent(before_node),
1391 MappingEntryStyle::Indent(indent) => indent,
1392 };
1393 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1394 let replacement =
1395 self.format_mapping_entry_replacement(indent, key, value, comment, false, false)?;
1396
1397 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1398 }
1399
1400 pub fn insert_mapping_value_before_with_comment<T>(
1407 &mut self,
1408 before_entry: NodeId,
1409 key: &str,
1410 value: &T,
1411 style: MappingEntryStyle,
1412 comment: Option<&str>,
1413 ) -> Result<(), YamlError>
1414 where
1415 T: ToYamlFragment,
1416 {
1417 let before_node = self.expect_node_kind(before_entry, NodeKind::MappingEntry)?;
1418 let mapping = before_node.parent().ok_or_else(|| {
1419 YamlError::new(
1420 Diagnostic::new(
1421 DiagnosticKind::Semantic,
1422 "mapping entry has no parent mapping",
1423 before_node.span,
1424 )
1425 .with_expected("a mapping parent"),
1426 )
1427 })?;
1428 if matches!(
1429 self.semantic_kind(mapping),
1430 Some(SemanticKind::Mapping {
1431 style: CollectionStyle::Flow
1432 })
1433 ) {
1434 let fragment = self.typed_value_fragment(value)?;
1435 return self
1436 .queue_mapping_insert_before(mapping, before_entry, key, &fragment)
1437 .map_err(YamlEditError::into_yaml_error);
1438 }
1439 let indent = match style {
1440 MappingEntryStyle::Inherit => self.node_indent(before_node),
1441 MappingEntryStyle::Indent(indent) => indent,
1442 };
1443 let insertion_offset = self.line_start_for_offset(before_node.span.start as usize);
1444 let replacement =
1445 self.format_mapping_value_replacement(indent, key, value, comment, false, false)?;
1446
1447 self.queue_edit(Span::empty_from_usize(insertion_offset), replacement)
1448 }
1449
1450 pub fn insert_mapping_entry_ordered_with_comment(
1462 &mut self,
1463 mapping: NodeId,
1464 key: &str,
1465 value: &str,
1466 style: MappingEntryStyle,
1467 comment: Option<&str>,
1468 ordered_keys: &[&str],
1469 ) -> Result<(), YamlError> {
1470 let mut next_entry = None;
1471 if let Some(position) = ordered_keys.iter().position(|ordered| *ordered == key) {
1472 for later_key in &ordered_keys[position + 1..] {
1473 if let Some(entry) = self.get_mapping_entry(mapping, later_key)? {
1474 next_entry = Some(entry);
1475 break;
1476 }
1477 }
1478 }
1479
1480 if let Some(next_entry) = next_entry {
1481 self.insert_mapping_entry_before_with_comment(next_entry, key, value, style, comment)
1482 } else {
1483 self.insert_mapping_entry_with_comment(mapping, key, value, style, comment)
1484 }
1485 }
1486
1487 pub fn remove_mapping_entry(&mut self, mapping: NodeId, key: &str) -> Result<(), YamlError> {
1497 let Some(entry) = self.get_mapping_entry(mapping, key)? else {
1498 return Ok(());
1499 };
1500 self.remove_collection_entries(mapping, &[entry])
1501 }
1502
1503 pub fn retain_mapping_entries(
1514 &mut self,
1515 mapping: NodeId,
1516 allowed_keys: &[&str],
1517 ) -> Result<(), YamlError> {
1518 let mapping_node = self.expect_node(mapping)?;
1519 if !matches!(
1520 mapping_node.kind,
1521 NodeKind::BlockMapping | NodeKind::FlowMapping
1522 ) {
1523 return Err(YamlError::new(
1524 Diagnostic::new(
1525 DiagnosticKind::Semantic,
1526 format!("expected mapping, found {:?}", mapping_node.kind),
1527 mapping_node.span,
1528 )
1529 .with_expected("BlockMapping or FlowMapping"),
1530 ));
1531 }
1532 let mut removals = Vec::new();
1533
1534 for entry in self.children(mapping) {
1535 let entry_node = self.expect_node(entry)?;
1536 if entry_node.kind != NodeKind::MappingEntry {
1537 continue;
1538 }
1539
1540 let Some(key_node) = self.children(entry).next() else {
1541 continue;
1542 };
1543 let key = self.scalar_value(key_node)?;
1544 if !allowed_keys.contains(&key.as_ref()) {
1545 removals.push(entry);
1546 }
1547 }
1548
1549 self.remove_collection_entries(mapping, &removals)
1550 }
1551
1552 pub(crate) fn remove_collection_entries(
1553 &mut self,
1554 collection: NodeId,
1555 removals: &[NodeId],
1556 ) -> Result<(), YamlError> {
1557 if removals.is_empty() {
1558 return Ok(());
1559 }
1560 let Some(style) = (match self.semantic_kind(collection) {
1561 Some(SemanticKind::Mapping { style } | SemanticKind::Sequence { style }) => Some(style),
1562 _ => None,
1563 }) else {
1564 return Err(YamlError::new(
1565 Diagnostic::new(
1566 DiagnosticKind::Semantic,
1567 "collection entry removal target is not a mapping or sequence",
1568 self.expect_node(collection)?.span,
1569 )
1570 .with_expected("a mapping or sequence"),
1571 ));
1572 };
1573 if style == CollectionStyle::Block {
1574 for entry in removals {
1575 self.remove_node(*entry)?;
1576 }
1577 return Ok(());
1578 }
1579
1580 let entries = self
1581 .children(collection)
1582 .filter(|node| {
1583 self.node(*node).is_some_and(|node| {
1584 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1585 })
1586 })
1587 .collect::<Vec<_>>();
1588 if removals.len() == entries.len() {
1589 let collection_node = self.expect_node(collection)?;
1590 let delimiter = match collection_node.kind {
1591 NodeKind::FlowMapping => '}',
1592 NodeKind::FlowSequence => ']',
1593 _ => unreachable!("flow semantic collection must have a flow CST node"),
1594 };
1595 if let Some(relative) = self.source.slice(collection_node.span).rfind(delimiter) {
1596 let close = Span::offset_from_usize(collection_node.span.start, relative);
1597 if let Some(edit) = self
1598 .edits
1599 .iter_mut()
1600 .find(|edit| edit.span == Span::empty(close))
1601 && let Some(replacement) = edit.replacement.strip_prefix(", ")
1602 {
1603 edit.replacement = replacement.to_owned();
1604 }
1605 }
1606 }
1607 let mut index = 0;
1608 while index < entries.len() {
1609 if !removals.contains(&entries[index]) {
1610 index += 1;
1611 continue;
1612 }
1613 let start = index;
1614 while index < entries.len() && removals.contains(&entries[index]) {
1615 index += 1;
1616 }
1617 let end = index;
1618 let first = self.expect_node(entries[start])?.span;
1619 let last = self.expect_node(entries[end - 1])?.span;
1620 let span = if let Some(next) = entries.get(end).copied() {
1621 Span::new(first.start, self.expect_node(next)?.span.start)
1622 } else if start > 0 {
1623 Span::new(self.expect_node(entries[start - 1])?.span.end, last.end)
1624 } else {
1625 Span::new(first.start, last.end)
1626 };
1627 self.queue_edit(span, String::new())?;
1628 }
1629 Ok(())
1630 }
1631
1632 pub fn remove_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1642 let node = self.expect_node(node)?;
1643 let span = if matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry) {
1644 self.line_span_including_break(node.span)
1645 } else {
1646 node.span
1647 };
1648 self.queue_edit(span, String::new())
1649 }
1650
1651 pub(crate) fn scalar_replacement_target(
1652 &self,
1653 node: NodeId,
1654 ) -> Result<(Span, ScalarStyle), YamlError> {
1655 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1656 let text = self.source.slice(node.span);
1657 let properties = parse_node_properties(text, node.span)?;
1658 let value_text = &text[properties.value_start()..];
1659 let value_start = Span::offset_from_usize(node.span.start, properties.value_start());
1660
1661 if value_text.starts_with('"') {
1662 let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1663 YamlError::new(
1664 Diagnostic::new(
1665 DiagnosticKind::Emitter,
1666 "could not find the end of the double-quoted scalar",
1667 node.span,
1668 )
1669 .with_expected("a closed double-quoted scalar"),
1670 )
1671 })?;
1672 return Ok((
1673 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1674 ScalarStyle::DoubleQuoted,
1675 ));
1676 }
1677
1678 if value_text.starts_with('\'') {
1679 let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1680 YamlError::new(
1681 Diagnostic::new(
1682 DiagnosticKind::Emitter,
1683 "could not find the end of the single-quoted scalar",
1684 node.span,
1685 )
1686 .with_expected("a closed single-quoted scalar"),
1687 )
1688 })?;
1689 return Ok((
1690 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1691 ScalarStyle::SingleQuoted,
1692 ));
1693 }
1694
1695 let end = plain_scalar_end(value_text);
1696 if end == 0 {
1697 return Err(YamlError::new(
1698 Diagnostic::new(
1699 DiagnosticKind::Emitter,
1700 "could not find plain scalar text to replace",
1701 node.span,
1702 )
1703 .with_expected("plain scalar text"),
1704 ));
1705 }
1706
1707 Ok((
1708 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1709 ScalarStyle::Plain,
1710 ))
1711 }
1712
1713 fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1714 self.root()
1715 .into_iter()
1716 .flat_map(|root| self.children(root))
1717 .filter(|node| {
1718 self.node(*node)
1719 .is_some_and(|node| node.kind == NodeKind::Directive)
1720 })
1721 }
1722
1723 fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1724 let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1725 let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1726 let mut parts = body.split_whitespace();
1727 let Some(name) = parts.next() else {
1728 return Err(directive_emit_error(
1729 "directive is missing a name",
1730 node_ref.span,
1731 "%YAML, %TAG, or reserved directive syntax",
1732 )
1733 .with_position_from(&self.source));
1734 };
1735
1736 Ok(match name {
1737 "%YAML" => ParsedDirective::Yaml(YamlDirective {
1738 version: parts.next().unwrap_or_default().to_owned(),
1739 node,
1740 }),
1741 "%TAG" => ParsedDirective::Tag(TagDirective {
1742 handle: parts.next().unwrap_or_default().to_owned(),
1743 prefix: parts.next().unwrap_or_default().to_owned(),
1744 node,
1745 }),
1746 _ => ParsedDirective::Reserved(ReservedDirective {
1747 name: name.to_owned(),
1748 parameters: parts.map(str::to_owned).collect(),
1749 node,
1750 }),
1751 })
1752 }
1753
1754 fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1755 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1756 let text = self.source.slice(node.span);
1757 let end = strip_inline_comment(text)
1758 .trim_end_matches([' ', '\t'])
1759 .len();
1760 Ok(Span::new(
1761 node.span.start,
1762 Span::offset_from_usize(node.span.start, end),
1763 ))
1764 }
1765
1766 fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1767 let insertion_offset = self.directive_insertion_offset();
1768 let mut line = replacement;
1769 line.push_str(self.preferred_line_ending());
1770 self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1771 }
1772
1773 fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1774 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1775 self.queue_edit(self.line_span_including_break(node.span), String::new())
1776 }
1777
1778 fn directive_insertion_offset(&self) -> usize {
1779 if let Some(last_directive) = self
1780 .directive_nodes()
1781 .filter_map(|node| self.node(node))
1782 .max_by_key(|node| node.span.start)
1783 {
1784 return self.line_span_including_break(last_directive.span).end as usize;
1785 }
1786
1787 self.root()
1788 .and_then(|root| self.children(root).next())
1789 .and_then(|node| self.node(node))
1790 .map_or(0, |node| {
1791 self.line_start_for_offset(node.span.start as usize)
1792 })
1793 }
1794
1795 pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1796 self.node(node).ok_or_else(|| {
1797 YamlError::new(Diagnostic::new(
1798 DiagnosticKind::Semantic,
1799 format!("unknown node id {}", node.0),
1800 Span::empty_from_usize(self.source.len()),
1801 ))
1802 })
1803 }
1804
1805 pub(crate) fn expect_node_kind(
1806 &self,
1807 node: NodeId,
1808 expected: NodeKind,
1809 ) -> Result<&Node, YamlError> {
1810 let actual = self.expect_node(node)?;
1811 if actual.kind == expected {
1812 Ok(actual)
1813 } else {
1814 Err(YamlError::new(
1815 Diagnostic::new(
1816 DiagnosticKind::Semantic,
1817 format!("expected {expected:?}, found {:?}", actual.kind),
1818 actual.span,
1819 )
1820 .with_expected(format!("{expected:?}")),
1821 )
1822 .with_position_from(&self.source))
1823 }
1824 }
1825
1826 pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1827 self.node(value).and_then(Node::parent).filter(|parent| {
1828 self.node(*parent).is_some_and(|node| {
1829 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1830 })
1831 })
1832 }
1833
1834 fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1835 let start = self.line_start_for_offset(mapping.span.start as usize);
1836 let end = mapping.span.end as usize;
1837 let text = &self.source.as_str()[start..end];
1838 text.contains("\n\n") || text.contains("\r\n\r\n")
1839 }
1840
1841 fn format_mapping_entry_replacement(
1842 &self,
1843 indent: usize,
1844 key: &str,
1845 value: &str,
1846 comment: Option<&str>,
1847 needs_leading_break: bool,
1848 preserve_paragraph_break: bool,
1849 ) -> Result<String, YamlError> {
1850 validate_plain_mapping_fragment(key, "mapping key")?;
1851 validate_plain_mapping_fragment(value, "mapping value")?;
1852 if let Some(comment) = comment {
1853 validate_yaml_chars(comment)?;
1854 }
1855
1856 let line_ending = self.preferred_line_ending();
1857 let indent_text = " ".repeat(indent);
1858 let mut replacement = String::new();
1859 if needs_leading_break {
1860 replacement.push_str(line_ending);
1861 }
1862 if preserve_paragraph_break {
1863 replacement.push_str(line_ending);
1864 }
1865 if let Some(comment) = comment {
1866 for line in comment.lines() {
1867 replacement.push_str(&indent_text);
1868 replacement.push('#');
1869 if !line.is_empty() {
1870 replacement.push(' ');
1871 replacement.push_str(line.trim());
1872 }
1873 replacement.push_str(line_ending);
1874 }
1875 }
1876 replacement.push_str(&indent_text);
1877 replacement.push_str(key);
1878 replacement.push_str(": ");
1879 replacement.push_str(value);
1880 replacement.push_str(line_ending);
1881 Ok(replacement)
1882 }
1883
1884 fn format_mapping_value_replacement<T>(
1885 &self,
1886 indent: usize,
1887 key: &str,
1888 value: &T,
1889 comment: Option<&str>,
1890 needs_leading_break: bool,
1891 preserve_paragraph_break: bool,
1892 ) -> Result<String, YamlError>
1893 where
1894 T: ToYamlFragment,
1895 {
1896 validate_yaml_chars(key)?;
1897 if let Some(comment) = comment {
1898 validate_yaml_chars(comment)?;
1899 }
1900
1901 let line_ending = self.preferred_line_ending();
1902 let indent_text = " ".repeat(indent);
1903 let child_indent = indent + 2;
1904 let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
1905 let mut replacement = String::new();
1906 if needs_leading_break {
1907 replacement.push_str(line_ending);
1908 }
1909 if preserve_paragraph_break {
1910 replacement.push_str(line_ending);
1911 }
1912 if let Some(comment) = comment {
1913 for line in comment.lines() {
1914 replacement.push_str(&indent_text);
1915 replacement.push('#');
1916 if !line.is_empty() {
1917 replacement.push(' ');
1918 replacement.push_str(line.trim());
1919 }
1920 replacement.push_str(line_ending);
1921 }
1922 }
1923 replacement.push_str(&indent_text);
1924 replacement.push_str(&crate::edit::emit_string_key(key));
1925 if fragment.contains('\n') || fragment.starts_with(' ') {
1926 replacement.push(':');
1927 replacement.push_str(line_ending);
1928 replacement.push_str(&fragment);
1929 } else {
1930 replacement.push_str(": ");
1931 replacement.push_str(&fragment);
1932 }
1933 replacement.push_str(line_ending);
1934 Ok(replacement)
1935 }
1936
1937 pub(crate) fn node_indent(&self, node: &Node) -> usize {
1938 let line_start = self.line_start_for_offset(node.span.start as usize);
1939 self.source.as_str()[line_start..node.span.start as usize]
1940 .bytes()
1941 .filter(|byte| *byte == b' ')
1942 .count()
1943 }
1944
1945 fn line_start_for_offset(&self, offset: usize) -> usize {
1946 let offset = Span::usize_to_u32(offset);
1947 match self.source.line_starts().binary_search(&offset) {
1948 Ok(index) => self.source.line_starts()[index] as usize,
1949 Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
1950 }
1951 }
1952
1953 pub(crate) fn find_nested_collection_after(
1954 &self,
1955 entry: &Node,
1956 parent_indent: usize,
1957 ) -> Option<NodeId> {
1958 self.nodes
1959 .iter()
1960 .enumerate()
1961 .filter(|(_, node)| {
1962 matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
1963 && node.span.start >= entry.span.end
1964 && self.node_indent(node) > parent_indent
1965 })
1966 .min_by_key(|(_, node)| node.span.start)
1967 .map(|(index, _)| NodeId::from_usize(index))
1968 }
1969
1970 pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
1971 let text = self.source.slice(scalar.span);
1972 let header_end = text.find(['\r', '\n'])?;
1973 let mut rest = &text[header_end..];
1974 while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
1975 rest = stripped;
1976 }
1977 for line in rest.lines() {
1978 if line.trim().is_empty() {
1979 continue;
1980 }
1981 return Some(line.bytes().take_while(|byte| *byte == b' ').count());
1982 }
1983 None
1984 }
1985
1986 pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
1987 self.source.try_slice(span)?;
1988 validate_yaml_chars(&replacement)?;
1989
1990 if span.is_empty()
1991 && let Some(existing) = self
1992 .edits
1993 .iter_mut()
1994 .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
1995 {
1996 existing.replacement.push_str(&replacement);
1997 return Ok(());
1998 }
1999
2000 if let Some(existing) = self
2001 .edits
2002 .iter()
2003 .find(|edit| edits_conflict(edit.span, span))
2004 {
2005 return Err(YamlError::new(
2006 Diagnostic::new(
2007 DiagnosticKind::Emitter,
2008 "edit overlaps an existing pending edit",
2009 span,
2010 )
2011 .with_note(format!(
2012 "existing edit covers bytes {}..{}",
2013 existing.span.start, existing.span.end
2014 )),
2015 )
2016 .with_position_from(&self.source));
2017 }
2018
2019 self.edits.push(Edit { span, replacement });
2020 Ok(())
2021 }
2022
2023 pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2024 node_link(mapping.last_child)
2025 .and_then(|child| self.node(child))
2026 .map_or(mapping.span.end as usize, |last_child| {
2027 self.line_span_including_break(last_child.span).end as usize
2028 })
2029 }
2030
2031 pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2032 node_link(sequence.last_child)
2033 .and_then(|child| self.node(child))
2034 .map_or(sequence.span.end as usize, |last_child| {
2035 self.line_span_including_break(last_child.span).end as usize
2036 })
2037 }
2038
2039 fn line_span_including_break(&self, span: Span) -> Span {
2040 let start = self.line_start_for_offset(span.start as usize);
2041 let mut end = span.end as usize;
2042 let bytes = self.source.as_str().as_bytes();
2043
2044 if end < bytes.len() {
2045 if bytes[end] == b'\r' {
2046 end += 1;
2047 if end < bytes.len() && bytes[end] == b'\n' {
2048 end += 1;
2049 }
2050 } else if bytes[end] == b'\n' {
2051 end += 1;
2052 }
2053 }
2054
2055 Span::from_usize(start, end)
2056 }
2057
2058 pub(crate) fn preferred_line_ending(&self) -> &str {
2059 let bytes = self.source.as_str().as_bytes();
2060 for (index, byte) in bytes.iter().enumerate() {
2061 if *byte == b'\r' {
2062 return if bytes.get(index + 1) == Some(&b'\n') {
2063 "\r\n"
2064 } else {
2065 "\r"
2066 };
2067 }
2068 if *byte == b'\n' {
2069 return if index > 0 && bytes[index - 1] == b'\r' {
2070 "\r\n"
2071 } else {
2072 "\n"
2073 };
2074 }
2075 }
2076 "\n"
2077 }
2078
2079 fn source_ends_with_line_break(&self) -> bool {
2080 self.source
2081 .as_str()
2082 .as_bytes()
2083 .last()
2084 .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2085 }
2086
2087 fn document_append_prefix(&self, line_ending: &str) -> String {
2088 if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2089 String::new()
2090 } else {
2091 line_ending.to_owned()
2092 }
2093 }
2094}
2095impl fmt::Display for YamlDoc {
2096 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2097 if self.edits.is_empty() {
2098 return formatter.write_str(self.source.as_str());
2099 }
2100
2101 let mut output = self.source.as_str().to_owned();
2102 let mut edits = self.edits.clone();
2103 edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2104
2105 for edit in edits {
2106 output.replace_range(
2107 edit.span.start as usize..edit.span.end as usize,
2108 &edit.replacement,
2109 );
2110 }
2111
2112 formatter.write_str(&output)
2113 }
2114}