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 pub(crate) 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.block_mapping_entry_indent(mapping),
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.block_mapping_entry_indent(mapping),
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_column(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_column(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 block_collection_entry_removal_span(
1652 &self,
1653 collection: NodeId,
1654 entry: NodeId,
1655 ) -> Result<Span, YamlError> {
1656 let collection_node = self.expect_node(collection)?;
1657 let entry_node = self.expect_node(entry)?;
1658 let entry_kind = match self.semantic_kind(collection) {
1659 Some(SemanticKind::Mapping { .. }) => NodeKind::MappingEntry,
1660 Some(SemanticKind::Sequence { .. }) => NodeKind::SequenceEntry,
1661 _ => {
1662 return Err(YamlError::new(
1663 Diagnostic::new(
1664 DiagnosticKind::Semantic,
1665 "block entry parent is not a semantic collection",
1666 collection_node.span,
1667 )
1668 .with_expected("a block mapping or sequence"),
1669 ));
1670 }
1671 };
1672 let entries = self
1673 .children(collection)
1674 .filter(|child| {
1675 self.node(*child)
1676 .is_some_and(|node| node.kind == entry_kind)
1677 })
1678 .collect::<Vec<_>>();
1679 let index = entries
1680 .iter()
1681 .position(|candidate| *candidate == entry)
1682 .ok_or_else(|| {
1683 YamlError::new(
1684 Diagnostic::new(
1685 DiagnosticKind::Semantic,
1686 "block collection entry is missing from its parent",
1687 entry_node.span,
1688 )
1689 .with_expected("an entry owned by the selected collection"),
1690 )
1691 })?;
1692 let compact_sequence_mapping = entry_kind == NodeKind::MappingEntry
1693 && self
1694 .node(collection)
1695 .and_then(Node::parent)
1696 .and_then(|parent| self.node(parent))
1697 .is_some_and(|parent| parent.kind == NodeKind::SequenceEntry);
1698 if entries.len() == 1 {
1699 return Ok(Span::new(
1700 collection_node.span.start,
1701 Span::usize_to_u32(self.block_entry_extent_end(entries[0])?),
1702 ));
1703 }
1704 let start = if compact_sequence_mapping && index == 0 {
1705 collection_node.span.start as usize
1706 } else {
1707 self.line_start_for_offset(entry_node.span.start as usize)
1708 };
1709 let end = if let Some(next) = entries.get(index + 1).copied() {
1710 if compact_sequence_mapping && index == 0 {
1711 self.collection_entry_content_start(next)?
1712 } else {
1713 self.line_start_for_offset(self.expect_node(next)?.span.start as usize)
1714 }
1715 } else {
1716 self.block_entry_extent_end(entry)?
1717 };
1718 Ok(Span::from_usize(start, end))
1719 }
1720
1721 pub(crate) fn collection_entry_content_start(&self, entry: NodeId) -> Result<usize, YamlError> {
1722 let entry_node = self.expect_node(entry)?;
1723 Ok(self
1724 .semantic_children(entry)
1725 .next()
1726 .and_then(|child| self.node(child))
1727 .map_or(entry_node.span.start as usize, |child| {
1728 child.span.start as usize
1729 }))
1730 }
1731
1732 fn block_entry_extent_end(&self, entry: NodeId) -> Result<usize, YamlError> {
1733 let source = self.source.as_str();
1734 let entry_node = self.expect_node(entry)?;
1735 let content_start = if entry_node.kind == NodeKind::MappingEntry {
1736 self.collection_entry_content_start(entry)?
1737 } else {
1738 entry_node.span.start as usize
1739 };
1740 let line_start = self.line_start_for_offset(content_start);
1741 let entry_indent = source[line_start..content_start]
1742 .bytes()
1743 .take_while(|byte| *byte == b' ')
1744 .count();
1745 let line_index = self
1746 .source
1747 .line_starts()
1748 .binary_search(&Span::usize_to_u32(line_start))
1749 .expect("entry line start is indexed");
1750 for next_start in self.source.line_starts().iter().skip(line_index + 1) {
1751 let next_start = *next_start as usize;
1752 let line_end = source[next_start..]
1753 .find(['\r', '\n'])
1754 .map_or(source.len(), |relative| next_start + relative);
1755 let line = &source[next_start..line_end];
1756 if line.trim().is_empty() {
1757 continue;
1758 }
1759 let indent = line.bytes().take_while(|byte| *byte == b' ').count();
1760 if indent <= entry_indent {
1761 return Ok(next_start);
1762 }
1763 }
1764 Ok(source.len())
1765 }
1766
1767 pub(crate) fn scalar_replacement_target(
1768 &self,
1769 node: NodeId,
1770 ) -> Result<(Span, ScalarStyle), YamlError> {
1771 let node = self.expect_node_kind(node, NodeKind::Scalar)?;
1772 let text = self.source.slice(node.span);
1773 let properties = parse_node_properties(text, node.span)?;
1774 let value_text = &text[properties.value_start()..];
1775 let value_start = Span::offset_from_usize(node.span.start, properties.value_start());
1776
1777 if value_text.starts_with('"') {
1778 let end = double_quoted_scalar_end(value_text).ok_or_else(|| {
1779 YamlError::new(
1780 Diagnostic::new(
1781 DiagnosticKind::Emitter,
1782 "could not find the end of the double-quoted scalar",
1783 node.span,
1784 )
1785 .with_expected("a closed double-quoted scalar"),
1786 )
1787 })?;
1788 return Ok((
1789 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1790 ScalarStyle::DoubleQuoted,
1791 ));
1792 }
1793
1794 if value_text.starts_with('\'') {
1795 let end = single_quoted_scalar_end(value_text).ok_or_else(|| {
1796 YamlError::new(
1797 Diagnostic::new(
1798 DiagnosticKind::Emitter,
1799 "could not find the end of the single-quoted scalar",
1800 node.span,
1801 )
1802 .with_expected("a closed single-quoted scalar"),
1803 )
1804 })?;
1805 return Ok((
1806 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1807 ScalarStyle::SingleQuoted,
1808 ));
1809 }
1810
1811 let end = plain_scalar_end(value_text);
1812 if end == 0 {
1813 return Err(YamlError::new(
1814 Diagnostic::new(
1815 DiagnosticKind::Emitter,
1816 "could not find plain scalar text to replace",
1817 node.span,
1818 )
1819 .with_expected("plain scalar text"),
1820 ));
1821 }
1822
1823 Ok((
1824 Span::new(value_start, Span::offset_from_usize(value_start, end)),
1825 ScalarStyle::Plain,
1826 ))
1827 }
1828
1829 pub(crate) fn node_value_start(&self, node: NodeId) -> Result<usize, YamlError> {
1830 let node = self.expect_node(node)?;
1831 let properties = parse_node_properties(self.source.slice(node.span), node.span)?;
1832 Ok(node.span.start as usize + properties.value_start())
1833 }
1834
1835 fn directive_nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
1836 self.root()
1837 .into_iter()
1838 .flat_map(|root| self.children(root))
1839 .filter(|node| {
1840 self.node(*node)
1841 .is_some_and(|node| node.kind == NodeKind::Directive)
1842 })
1843 }
1844
1845 fn parse_directive_node(&self, node: NodeId) -> Result<ParsedDirective, YamlError> {
1846 let node_ref = self.expect_node_kind(node, NodeKind::Directive)?;
1847 let body = strip_inline_comment(self.source.slice(node_ref.span)).trim();
1848 let mut parts = body.split_whitespace();
1849 let Some(name) = parts.next() else {
1850 return Err(directive_emit_error(
1851 "directive is missing a name",
1852 node_ref.span,
1853 "%YAML, %TAG, or reserved directive syntax",
1854 )
1855 .with_position_from(&self.source));
1856 };
1857
1858 Ok(match name {
1859 "%YAML" => ParsedDirective::Yaml(YamlDirective {
1860 version: parts.next().unwrap_or_default().to_owned(),
1861 node,
1862 }),
1863 "%TAG" => ParsedDirective::Tag(TagDirective {
1864 handle: parts.next().unwrap_or_default().to_owned(),
1865 prefix: parts.next().unwrap_or_default().to_owned(),
1866 node,
1867 }),
1868 _ => ParsedDirective::Reserved(ReservedDirective {
1869 name: name.to_owned(),
1870 parameters: parts.map(str::to_owned).collect(),
1871 node,
1872 }),
1873 })
1874 }
1875
1876 fn directive_content_span(&self, node: NodeId) -> Result<Span, YamlError> {
1877 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1878 let text = self.source.slice(node.span);
1879 let end = strip_inline_comment(text)
1880 .trim_end_matches([' ', '\t'])
1881 .len();
1882 Ok(Span::new(
1883 node.span.start,
1884 Span::offset_from_usize(node.span.start, end),
1885 ))
1886 }
1887
1888 fn insert_directive_line(&mut self, replacement: String) -> Result<(), YamlError> {
1889 let insertion_offset = self.directive_insertion_offset();
1890 let mut line = replacement;
1891 line.push_str(self.preferred_line_ending());
1892 self.queue_edit(Span::empty_from_usize(insertion_offset), line)
1893 }
1894
1895 fn remove_directive_node(&mut self, node: NodeId) -> Result<(), YamlError> {
1896 let node = self.expect_node_kind(node, NodeKind::Directive)?;
1897 self.queue_edit(self.line_span_including_break(node.span), String::new())
1898 }
1899
1900 fn directive_insertion_offset(&self) -> usize {
1901 if let Some(last_directive) = self
1902 .directive_nodes()
1903 .filter_map(|node| self.node(node))
1904 .max_by_key(|node| node.span.start)
1905 {
1906 return self.line_span_including_break(last_directive.span).end as usize;
1907 }
1908
1909 self.root()
1910 .and_then(|root| self.children(root).next())
1911 .and_then(|node| self.node(node))
1912 .map_or(0, |node| {
1913 self.line_start_for_offset(node.span.start as usize)
1914 })
1915 }
1916
1917 pub(crate) fn expect_node(&self, node: NodeId) -> Result<&Node, YamlError> {
1918 self.node(node).ok_or_else(|| {
1919 YamlError::new(Diagnostic::new(
1920 DiagnosticKind::Semantic,
1921 format!("unknown node id {}", node.0),
1922 Span::empty_from_usize(self.source.len()),
1923 ))
1924 })
1925 }
1926
1927 pub(crate) fn expect_node_kind(
1928 &self,
1929 node: NodeId,
1930 expected: NodeKind,
1931 ) -> Result<&Node, YamlError> {
1932 let actual = self.expect_node(node)?;
1933 if actual.kind == expected {
1934 Ok(actual)
1935 } else {
1936 Err(YamlError::new(
1937 Diagnostic::new(
1938 DiagnosticKind::Semantic,
1939 format!("expected {expected:?}, found {:?}", actual.kind),
1940 actual.span,
1941 )
1942 .with_expected(format!("{expected:?}")),
1943 )
1944 .with_position_from(&self.source))
1945 }
1946 }
1947
1948 pub(crate) fn containing_entry(&self, value: NodeId) -> Option<NodeId> {
1949 self.node(value).and_then(Node::parent).filter(|parent| {
1950 self.node(*parent).is_some_and(|node| {
1951 matches!(node.kind, NodeKind::MappingEntry | NodeKind::SequenceEntry)
1952 })
1953 })
1954 }
1955
1956 fn mapping_has_blank_line(&self, mapping: &Node) -> bool {
1957 let start = self.line_start_for_offset(mapping.span.start as usize);
1958 let end = mapping.span.end as usize;
1959 let text = &self.source.as_str()[start..end];
1960 text.contains("\n\n") || text.contains("\r\n\r\n")
1961 }
1962
1963 fn format_mapping_entry_replacement(
1964 &self,
1965 indent: usize,
1966 key: &str,
1967 value: &str,
1968 comment: Option<&str>,
1969 needs_leading_break: bool,
1970 preserve_paragraph_break: bool,
1971 ) -> Result<String, YamlError> {
1972 validate_plain_mapping_fragment(key, "mapping key")?;
1973 validate_plain_mapping_fragment(value, "mapping value")?;
1974 if let Some(comment) = comment {
1975 validate_yaml_chars(comment)?;
1976 }
1977
1978 let line_ending = self.preferred_line_ending();
1979 let indent_text = " ".repeat(indent);
1980 let mut replacement = String::new();
1981 if needs_leading_break {
1982 replacement.push_str(line_ending);
1983 }
1984 if preserve_paragraph_break {
1985 replacement.push_str(line_ending);
1986 }
1987 if let Some(comment) = comment {
1988 for line in comment.lines() {
1989 replacement.push_str(&indent_text);
1990 replacement.push('#');
1991 if !line.is_empty() {
1992 replacement.push(' ');
1993 replacement.push_str(line.trim());
1994 }
1995 replacement.push_str(line_ending);
1996 }
1997 }
1998 replacement.push_str(&indent_text);
1999 replacement.push_str(key);
2000 replacement.push_str(": ");
2001 replacement.push_str(value);
2002 replacement.push_str(line_ending);
2003 Ok(replacement)
2004 }
2005
2006 fn format_mapping_value_replacement<T>(
2007 &self,
2008 indent: usize,
2009 key: &str,
2010 value: &T,
2011 comment: Option<&str>,
2012 needs_leading_break: bool,
2013 preserve_paragraph_break: bool,
2014 ) -> Result<String, YamlError>
2015 where
2016 T: ToYamlFragment,
2017 {
2018 validate_yaml_chars(key)?;
2019 if let Some(comment) = comment {
2020 validate_yaml_chars(comment)?;
2021 }
2022
2023 let line_ending = self.preferred_line_ending();
2024 let indent_text = " ".repeat(indent);
2025 let child_indent = indent + 2;
2026 let fragment = value.to_yaml_fragment(child_indent, line_ending)?;
2027 let mut replacement = String::new();
2028 if needs_leading_break {
2029 replacement.push_str(line_ending);
2030 }
2031 if preserve_paragraph_break {
2032 replacement.push_str(line_ending);
2033 }
2034 if let Some(comment) = comment {
2035 for line in comment.lines() {
2036 replacement.push_str(&indent_text);
2037 replacement.push('#');
2038 if !line.is_empty() {
2039 replacement.push(' ');
2040 replacement.push_str(line.trim());
2041 }
2042 replacement.push_str(line_ending);
2043 }
2044 }
2045 replacement.push_str(&indent_text);
2046 replacement.push_str(&crate::edit::emit_string_key(key));
2047 if fragment.contains('\n') || fragment.starts_with(' ') {
2048 replacement.push(':');
2049 replacement.push_str(line_ending);
2050 replacement.push_str(&fragment);
2051 } else {
2052 replacement.push_str(": ");
2053 replacement.push_str(&fragment);
2054 }
2055 replacement.push_str(line_ending);
2056 Ok(replacement)
2057 }
2058
2059 pub(crate) fn node_indent(&self, node: &Node) -> usize {
2060 let line_start = self.line_start_for_offset(node.span.start as usize);
2061 self.source.as_str()[line_start..node.span.start as usize]
2062 .bytes()
2063 .filter(|byte| *byte == b' ')
2064 .count()
2065 }
2066
2067 pub(crate) fn block_mapping_entry_indent(&self, mapping: NodeId) -> usize {
2068 self.mapping_entries(mapping)
2069 .next()
2070 .and_then(|(key, _)| self.node(key).map(|node| self.node_column(node)))
2071 .or_else(|| self.node(mapping).map(|node| self.node_column(node)))
2072 .unwrap_or_default()
2073 }
2074
2075 fn node_column(&self, node: &Node) -> usize {
2076 node.span.start as usize - self.line_start_for_offset(node.span.start as usize)
2077 }
2078
2079 fn line_start_for_offset(&self, offset: usize) -> usize {
2080 let offset = Span::usize_to_u32(offset);
2081 match self.source.line_starts().binary_search(&offset) {
2082 Ok(index) => self.source.line_starts()[index] as usize,
2083 Err(index) => self.source.line_starts()[index.saturating_sub(1)] as usize,
2084 }
2085 }
2086
2087 pub(crate) fn find_nested_collection_after(
2088 &self,
2089 entry: &Node,
2090 parent_indent: usize,
2091 ) -> Option<NodeId> {
2092 self.nodes
2093 .iter()
2094 .enumerate()
2095 .filter(|(_, node)| {
2096 matches!(node.kind, NodeKind::BlockMapping | NodeKind::BlockSequence)
2097 && node.span.start >= entry.span.end
2098 && self.node_indent(node) > parent_indent
2099 })
2100 .min_by_key(|(_, node)| node.span.start)
2101 .map(|(index, _)| NodeId::from_usize(index))
2102 }
2103
2104 pub(crate) fn block_scalar_content_indent(&self, scalar: &Node) -> Option<usize> {
2105 let text = self.source.slice(scalar.span);
2106 let header_end = text.find(['\r', '\n'])?;
2107 let mut rest = &text[header_end..];
2108 while let Some(stripped) = rest.strip_prefix('\r').or_else(|| rest.strip_prefix('\n')) {
2109 rest = stripped;
2110 }
2111 for line in rest.lines() {
2112 if line.trim().is_empty() {
2113 continue;
2114 }
2115 return Some(line.bytes().take_while(|byte| *byte == b' ').count());
2116 }
2117 None
2118 }
2119
2120 pub(crate) fn queue_edit(&mut self, span: Span, replacement: String) -> Result<(), YamlError> {
2121 self.source.try_slice(span)?;
2122 validate_yaml_chars(&replacement)?;
2123
2124 if span.is_empty()
2125 && let Some(existing) = self
2126 .edits
2127 .iter_mut()
2128 .find(|edit| edit.span.is_empty() && edit.span.start == span.start)
2129 {
2130 existing.replacement.push_str(&replacement);
2131 return Ok(());
2132 }
2133
2134 if let Some(existing) = self
2135 .edits
2136 .iter()
2137 .find(|edit| edits_conflict(edit.span, span))
2138 {
2139 return Err(YamlError::new(
2140 Diagnostic::new(
2141 DiagnosticKind::Emitter,
2142 "edit overlaps an existing pending edit",
2143 span,
2144 )
2145 .with_note(format!(
2146 "existing edit covers bytes {}..{}",
2147 existing.span.start, existing.span.end
2148 )),
2149 )
2150 .with_position_from(&self.source));
2151 }
2152
2153 self.edits.push(Edit { span, replacement });
2154 Ok(())
2155 }
2156
2157 pub(crate) fn mapping_insertion_offset(&self, mapping: &Node) -> usize {
2158 node_link(mapping.last_child)
2159 .and_then(|child| self.node(child))
2160 .map_or(mapping.span.end as usize, |last_child| {
2161 self.line_span_including_break(last_child.span).end as usize
2162 })
2163 }
2164
2165 pub(crate) fn sequence_insertion_offset(&self, sequence: &Node) -> usize {
2166 node_link(sequence.last_child)
2167 .and_then(|child| self.node(child))
2168 .map_or(sequence.span.end as usize, |last_child| {
2169 self.line_span_including_break(last_child.span).end as usize
2170 })
2171 }
2172
2173 fn line_span_including_break(&self, span: Span) -> Span {
2174 let start = self.line_start_for_offset(span.start as usize);
2175 let mut end = span.end as usize;
2176 let bytes = self.source.as_str().as_bytes();
2177
2178 if end < bytes.len() {
2179 if bytes[end] == b'\r' {
2180 end += 1;
2181 if end < bytes.len() && bytes[end] == b'\n' {
2182 end += 1;
2183 }
2184 } else if bytes[end] == b'\n' {
2185 end += 1;
2186 }
2187 }
2188
2189 Span::from_usize(start, end)
2190 }
2191
2192 pub(crate) fn preferred_line_ending(&self) -> &str {
2193 let bytes = self.source.as_str().as_bytes();
2194 for (index, byte) in bytes.iter().enumerate() {
2195 if *byte == b'\r' {
2196 return if bytes.get(index + 1) == Some(&b'\n') {
2197 "\r\n"
2198 } else {
2199 "\r"
2200 };
2201 }
2202 if *byte == b'\n' {
2203 return if index > 0 && bytes[index - 1] == b'\r' {
2204 "\r\n"
2205 } else {
2206 "\n"
2207 };
2208 }
2209 }
2210 "\n"
2211 }
2212
2213 fn source_ends_with_line_break(&self) -> bool {
2214 self.source
2215 .as_str()
2216 .as_bytes()
2217 .last()
2218 .is_some_and(|byte| matches!(byte, b'\n' | b'\r'))
2219 }
2220
2221 fn document_append_prefix(&self, line_ending: &str) -> String {
2222 if self.source.as_str().is_empty() || self.source_ends_with_line_break() {
2223 String::new()
2224 } else {
2225 line_ending.to_owned()
2226 }
2227 }
2228}
2229impl fmt::Display for YamlDoc {
2230 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2231 if self.edits.is_empty() {
2232 return formatter.write_str(self.source.as_str());
2233 }
2234
2235 let mut output = self.source.as_str().to_owned();
2236 let mut edits = self.edits.clone();
2237 edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
2238
2239 for edit in edits {
2240 output.replace_range(
2241 edit.span.start as usize..edit.span.end as usize,
2242 &edit.replacement,
2243 );
2244 }
2245
2246 formatter.write_str(&output)
2247 }
2248}