Skip to main content

panache_parser/syntax/
yaml.rs

1use std::ops::Range;
2
3use crate::parser::utils::yaml_regions::hashpipe_language_and_prefix;
4use crate::syntax::{AstNode, PanacheLanguage, SyntaxKind, SyntaxNode};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct YamlFrontmatterRegion {
8    pub id: String,
9    pub host_range: Range<usize>,
10    pub content_range: Range<usize>,
11    pub content: String,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum YamlRegionKind {
16    Frontmatter,
17    Hashpipe,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct YamlRegion {
22    pub id: String,
23    pub kind: YamlRegionKind,
24    pub host_range: Range<usize>,
25    pub region_range: Range<usize>,
26    pub content_range: Range<usize>,
27    pub content: String,
28    pub yaml_to_host_offsets: Vec<usize>,
29}
30
31#[derive(Debug, Clone)]
32pub struct ParsedYamlRegion {
33    region: YamlRegion,
34    parse_result: Result<yaml_parser::SyntaxNode, yaml_parser::SyntaxError>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ParsedYamlRegionSnapshot {
39    region: YamlRegion,
40    parse_ok: bool,
41    error: Option<YamlParseError>,
42    document_shape_summary: Option<String>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum YamlEmbeddingHostKind {
47    FrontmatterMetadata,
48    HashpipePreamble,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub struct YamlMetadata(SyntaxNode);
53
54impl AstNode for YamlMetadata {
55    type Language = PanacheLanguage;
56
57    fn can_cast(kind: SyntaxKind) -> bool {
58        kind == SyntaxKind::YAML_METADATA
59    }
60
61    fn cast(node: SyntaxNode) -> Option<Self> {
62        Self::can_cast(node.kind()).then(|| Self(node))
63    }
64
65    fn syntax(&self) -> &SyntaxNode {
66        &self.0
67    }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct HashpipeYamlPreamble(SyntaxNode);
72
73impl AstNode for HashpipeYamlPreamble {
74    type Language = PanacheLanguage;
75
76    fn can_cast(kind: SyntaxKind) -> bool {
77        kind == SyntaxKind::HASHPIPE_YAML_PREAMBLE
78    }
79
80    fn cast(node: SyntaxNode) -> Option<Self> {
81        Self::can_cast(node.kind()).then(|| Self(node))
82    }
83
84    fn syntax(&self) -> &SyntaxNode {
85        &self.0
86    }
87}
88
89#[derive(Debug, Clone)]
90pub enum YamlEmbeddingHost {
91    FrontmatterMetadata(YamlMetadata),
92    HashpipePreamble(HashpipeYamlPreamble),
93}
94
95#[derive(Debug, Clone)]
96pub struct YamlEmbeddedCst {
97    host: YamlEmbeddingHost,
98    parsed: ParsedYamlRegion,
99}
100
101impl YamlEmbeddedCst {
102    pub fn host_kind(&self) -> YamlEmbeddingHostKind {
103        match self.host {
104            YamlEmbeddingHost::FrontmatterMetadata(_) => YamlEmbeddingHostKind::FrontmatterMetadata,
105            YamlEmbeddingHost::HashpipePreamble(_) => YamlEmbeddingHostKind::HashpipePreamble,
106        }
107    }
108
109    pub fn host_node(&self) -> &SyntaxNode {
110        match &self.host {
111            YamlEmbeddingHost::FrontmatterMetadata(host) => host.syntax(),
112            YamlEmbeddingHost::HashpipePreamble(host) => host.syntax(),
113        }
114    }
115
116    pub fn frontmatter_host(&self) -> Option<&YamlMetadata> {
117        match &self.host {
118            YamlEmbeddingHost::FrontmatterMetadata(host) => Some(host),
119            _ => None,
120        }
121    }
122
123    pub fn hashpipe_host(&self) -> Option<&HashpipeYamlPreamble> {
124        match &self.host {
125            YamlEmbeddingHost::HashpipePreamble(host) => Some(host),
126            _ => None,
127        }
128    }
129
130    pub fn parsed(&self) -> &ParsedYamlRegion {
131        &self.parsed
132    }
133
134    pub fn yaml_content(&self) -> &str {
135        self.parsed.content()
136    }
137
138    pub fn host_offset_for_yaml_offset(&self, yaml_offset: usize) -> Option<usize> {
139        self.parsed.host_offset_for_yaml_offset(yaml_offset)
140    }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum YamlAstRootKind {
145    Root,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum YamlDocumentKind {
150    BlockMap,
151    BlockSeq,
152    BlockScalar,
153    Flow,
154    Empty,
155}
156
157#[derive(Debug, Clone, Copy)]
158pub struct YamlAstRoot<'a> {
159    node: &'a yaml_parser::SyntaxNode,
160}
161
162impl<'a> YamlAstRoot<'a> {
163    pub fn kind(&self) -> YamlAstRootKind {
164        debug_assert_eq!(self.node.kind(), yaml_parser::SyntaxKind::ROOT);
165        YamlAstRootKind::Root
166    }
167
168    pub fn document_count(&self) -> usize {
169        yaml_parser::ast::Root::cast(self.node.clone())
170            .map(|root| root.documents().count())
171            .unwrap_or(0)
172    }
173
174    pub fn first_document_kind(&self) -> Option<YamlDocumentKind> {
175        let root = yaml_parser::ast::Root::cast(self.node.clone())?;
176        let doc = root.documents().next()?;
177        if let Some(block) = doc.block() {
178            if block.block_map().is_some() {
179                return Some(YamlDocumentKind::BlockMap);
180            }
181            if block.block_seq().is_some() {
182                return Some(YamlDocumentKind::BlockSeq);
183            }
184            if block.block_scalar().is_some() {
185                return Some(YamlDocumentKind::BlockScalar);
186            }
187            return Some(YamlDocumentKind::Empty);
188        }
189        if doc.flow().is_some() {
190            return Some(YamlDocumentKind::Flow);
191        }
192        Some(YamlDocumentKind::Empty)
193    }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct YamlParseError {
198    offset: usize,
199    message: String,
200}
201
202impl YamlParseError {
203    pub fn offset(&self) -> usize {
204        self.offset
205    }
206
207    pub fn message(&self) -> &str {
208        &self.message
209    }
210
211    fn from_parser(err: &yaml_parser::SyntaxError) -> Self {
212        Self {
213            offset: err.offset(),
214            message: err.message().to_string(),
215        }
216    }
217}
218
219impl ParsedYamlRegion {
220    pub fn id(&self) -> &str {
221        &self.region.id
222    }
223
224    pub fn kind(&self) -> &YamlRegionKind {
225        &self.region.kind
226    }
227
228    pub fn is_frontmatter(&self) -> bool {
229        matches!(self.region.kind, YamlRegionKind::Frontmatter)
230    }
231
232    pub fn is_hashpipe(&self) -> bool {
233        matches!(self.region.kind, YamlRegionKind::Hashpipe)
234    }
235
236    pub fn root(&self) -> Option<YamlAstRoot<'_>> {
237        self.parse_result
238            .as_ref()
239            .ok()
240            .map(|node| YamlAstRoot { node })
241    }
242
243    pub fn error(&self) -> Option<YamlParseError> {
244        self.parse_result.as_ref().err().map(|err| YamlParseError {
245            offset: err.offset(),
246            message: err.message().to_string(),
247        })
248    }
249
250    pub fn root_kind(&self) -> Option<YamlAstRootKind> {
251        self.root().map(|root| root.kind())
252    }
253
254    pub fn is_valid(&self) -> bool {
255        self.parse_result.is_ok()
256    }
257
258    pub fn host_range(&self) -> Range<usize> {
259        self.region.host_range.clone()
260    }
261
262    pub fn content_range(&self) -> Range<usize> {
263        self.region.content_range.clone()
264    }
265
266    pub fn region_range(&self) -> Range<usize> {
267        self.region.region_range.clone()
268    }
269
270    pub fn to_region(&self) -> YamlRegion {
271        self.region.clone()
272    }
273
274    pub fn content(&self) -> &str {
275        &self.region.content
276    }
277
278    pub fn host_offset_for_yaml_offset(&self, yaml_offset: usize) -> Option<usize> {
279        self.region.yaml_to_host_offsets.get(yaml_offset).copied()
280    }
281
282    pub fn parse_error_host_offset(&self) -> Option<usize> {
283        self.error()
284            .and_then(|err| self.host_offset_for_yaml_offset(err.offset()))
285    }
286
287    pub fn document_shape_summary(&self) -> Option<String> {
288        let root = self.root()?;
289        let doc_count = root.document_count();
290        let first_kind = root.first_document_kind();
291        Some(match first_kind {
292            Some(kind) => format!("{:?} docs={} first={:?}", root.kind(), doc_count, kind),
293            None => format!("{:?} docs={}", root.kind(), doc_count),
294        })
295    }
296
297    pub fn to_snapshot(&self) -> ParsedYamlRegionSnapshot {
298        ParsedYamlRegionSnapshot {
299            region: self.region.clone(),
300            parse_ok: self.is_valid(),
301            error: self.error(),
302            document_shape_summary: self.document_shape_summary(),
303        }
304    }
305}
306
307impl ParsedYamlRegionSnapshot {
308    pub fn id(&self) -> &str {
309        &self.region.id
310    }
311
312    pub fn is_frontmatter(&self) -> bool {
313        matches!(self.region.kind, YamlRegionKind::Frontmatter)
314    }
315
316    pub fn is_hashpipe(&self) -> bool {
317        matches!(self.region.kind, YamlRegionKind::Hashpipe)
318    }
319
320    pub fn is_valid(&self) -> bool {
321        self.parse_ok
322    }
323
324    pub fn error(&self) -> Option<&YamlParseError> {
325        self.error.as_ref()
326    }
327
328    pub fn host_range(&self) -> Range<usize> {
329        self.region.host_range.clone()
330    }
331
332    pub fn parse_error_host_offset(&self) -> Option<usize> {
333        let err = self.error()?;
334        self.region.yaml_to_host_offsets.get(err.offset()).copied()
335    }
336
337    pub fn document_shape_summary(&self) -> Option<&str> {
338        self.document_shape_summary.as_deref()
339    }
340
341    pub fn to_region(&self) -> YamlRegion {
342        self.region.clone()
343    }
344}
345
346pub fn collect_frontmatter_region(tree: &SyntaxNode) -> Option<YamlFrontmatterRegion> {
347    let metadata = tree
348        .descendants()
349        .find(|node| node.kind() == SyntaxKind::YAML_METADATA)?;
350    let content_node = metadata
351        .children()
352        .find(|child| child.kind() == SyntaxKind::YAML_METADATA_CONTENT)?;
353
354    let host_start: usize = metadata.text_range().start().into();
355    let host_end: usize = metadata.text_range().end().into();
356    let content_start: usize = content_node.text_range().start().into();
357    let content_end: usize = content_node.text_range().end().into();
358
359    Some(YamlFrontmatterRegion {
360        id: format!("frontmatter:{}:{}", content_start, content_end),
361        host_range: host_start..host_end,
362        content_range: content_start..content_end,
363        content: content_node.text().to_string(),
364    })
365}
366
367pub fn collect_frontmatter_yaml_region(tree: &SyntaxNode) -> Option<YamlRegion> {
368    let frontmatter = collect_frontmatter_region(tree)?;
369    let content_range = frontmatter.content_range.clone();
370    Some(YamlRegion {
371        id: frontmatter.id,
372        kind: YamlRegionKind::Frontmatter,
373        host_range: frontmatter.host_range.clone(),
374        region_range: frontmatter.host_range,
375        content_range: content_range.clone(),
376        yaml_to_host_offsets: (0..=frontmatter.content.len())
377            .map(|offset| content_range.start + offset)
378            .collect(),
379        content: frontmatter.content,
380    })
381}
382
383pub fn collect_hashpipe_regions(tree: &SyntaxNode) -> Vec<YamlRegion> {
384    let mut regions = Vec::new();
385    for node in tree
386        .descendants()
387        .filter(|n| n.kind() == SyntaxKind::CODE_BLOCK)
388    {
389        let mut info_text: Option<String> = None;
390        let mut content_node: Option<SyntaxNode> = None;
391        for child in node.children() {
392            match child.kind() {
393                SyntaxKind::CODE_FENCE_OPEN => {
394                    for nested in child.children() {
395                        if nested.kind() == SyntaxKind::CODE_INFO {
396                            info_text = Some(nested.text().to_string());
397                        }
398                    }
399                }
400                SyntaxKind::CODE_CONTENT => content_node = Some(child),
401                _ => {}
402            }
403        }
404        let (Some(info_text), Some(content_node)) = (info_text, content_node) else {
405            continue;
406        };
407        let Some((language, prefix)) = hashpipe_language_and_prefix(&info_text) else {
408            continue;
409        };
410
411        let host_start: usize = node.text_range().start().into();
412        let host_end: usize = node.text_range().end().into();
413        let Some(preamble) = content_node
414            .children()
415            .find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_PREAMBLE)
416        else {
417            continue;
418        };
419        let Some(preamble_content) = preamble
420            .children()
421            .find(|n| n.kind() == SyntaxKind::HASHPIPE_YAML_CONTENT)
422        else {
423            continue;
424        };
425        let preamble_text = preamble_content.text().to_string();
426        let preamble_start: usize = preamble_content.text_range().start().into();
427        if let Some(region) = extract_hashpipe_region(
428            &preamble_text,
429            host_start,
430            host_end,
431            preamble_start,
432            prefix,
433            language.as_str(),
434        ) {
435            regions.push(region);
436        }
437    }
438    regions
439}
440
441pub fn collect_yaml_regions(tree: &SyntaxNode) -> Vec<YamlRegion> {
442    let mut regions = Vec::new();
443    if let Some(frontmatter) = collect_frontmatter_yaml_region(tree) {
444        regions.push(frontmatter);
445    }
446    regions.extend(collect_hashpipe_regions(tree));
447    regions
448}
449
450pub fn collect_parsed_yaml_regions(tree: &SyntaxNode) -> Vec<ParsedYamlRegion> {
451    collect_yaml_regions(tree)
452        .into_iter()
453        .map(|region| ParsedYamlRegion {
454            parse_result: yaml_parser::parse(&region.content),
455            region,
456        })
457        .collect()
458}
459
460pub fn collect_parsed_frontmatter_region(tree: &SyntaxNode) -> Option<ParsedYamlRegion> {
461    collect_parsed_yaml_regions(tree)
462        .into_iter()
463        .find(|region| region.is_frontmatter())
464}
465
466pub fn collect_parsed_yaml_region_snapshots(tree: &SyntaxNode) -> Vec<ParsedYamlRegionSnapshot> {
467    collect_parsed_yaml_regions(tree)
468        .iter()
469        .map(ParsedYamlRegion::to_snapshot)
470        .collect()
471}
472
473pub fn validate_yaml_text(input: &str) -> Result<(), YamlParseError> {
474    yaml_parser::parse(input)
475        .map(|_| ())
476        .map_err(|err| YamlParseError::from_parser(&err))
477}
478
479pub fn collect_embedded_yaml_cst(tree: &SyntaxNode) -> Vec<YamlEmbeddedCst> {
480    let parsed_regions = collect_parsed_yaml_regions(tree);
481    let frontmatter_node = tree.descendants().find_map(YamlMetadata::cast);
482    let hashpipe_preambles: Vec<HashpipeYamlPreamble> = tree
483        .descendants()
484        .filter_map(HashpipeYamlPreamble::cast)
485        .collect();
486
487    let mut embedded = Vec::new();
488    for parsed in parsed_regions {
489        match parsed.kind() {
490            YamlRegionKind::Frontmatter => {
491                if let Some(node) = frontmatter_node.clone() {
492                    embedded.push(YamlEmbeddedCst {
493                        host: YamlEmbeddingHost::FrontmatterMetadata(node),
494                        parsed,
495                    });
496                }
497            }
498            YamlRegionKind::Hashpipe => {
499                if let Some(node) = hashpipe_preambles.iter().find(|node| {
500                    let range: Range<usize> = node.syntax().text_range().start().into()
501                        ..node.syntax().text_range().end().into();
502                    range == parsed.region_range()
503                }) {
504                    embedded.push(YamlEmbeddedCst {
505                        host: YamlEmbeddingHost::HashpipePreamble(node.clone()),
506                        parsed,
507                    });
508                }
509            }
510        }
511    }
512    embedded
513}
514
515pub fn collect_embedded_frontmatter_yaml_cst(tree: &SyntaxNode) -> Option<YamlEmbeddedCst> {
516    collect_embedded_yaml_cst(tree)
517        .into_iter()
518        .find(|embedded| embedded.frontmatter_host().is_some())
519}
520
521fn extract_hashpipe_region(
522    content: &str,
523    host_start: usize,
524    host_end: usize,
525    content_start: usize,
526    prefix: &str,
527    language: &str,
528) -> Option<YamlRegion> {
529    let lines: Vec<&str> = content.split_inclusive('\n').collect();
530    if lines.is_empty() {
531        return None;
532    }
533    let mut collected = String::new();
534    let mut yaml_to_host_offsets = Vec::new();
535    let mut offset = 0usize;
536    for line in &lines {
537        let line = *line;
538        let line_core = line.strip_suffix('\n').unwrap_or(line);
539        let line_core = line_core.strip_suffix('\r').unwrap_or(line_core);
540        let eol = &line[line_core.len()..];
541        let indent_len = line_core
542            .chars()
543            .take_while(|ch| *ch == ' ' || *ch == '\t')
544            .map(char::len_utf8)
545            .sum::<usize>();
546        let trimmed = &line_core[indent_len..];
547        let after_prefix = trimmed.strip_prefix(prefix)?;
548        let payload = after_prefix
549            .strip_prefix(' ')
550            .or_else(|| after_prefix.strip_prefix('\t'))
551            .unwrap_or(after_prefix);
552        let after_prefix_start = indent_len + (trimmed.len() - after_prefix.len());
553        let payload_start = after_prefix_start + (after_prefix.len() - payload.len());
554        let line_host_start = content_start + offset;
555        yaml_to_host_offsets
556            .extend((0..payload.len()).map(|i| line_host_start + payload_start + i));
557        yaml_to_host_offsets.extend((0..eol.len()).map(|i| line_host_start + line_core.len() + i));
558        collected.push_str(payload);
559        collected.push_str(eol);
560        offset += line.len();
561    }
562    let start = content_start;
563    let region_end = content_start + offset;
564    yaml_to_host_offsets.push(region_end);
565    let id = format!("hashpipe:{}:{}:{}", language, host_start, start);
566    Some(YamlRegion {
567        id,
568        kind: YamlRegionKind::Hashpipe,
569        host_range: host_start..host_end,
570        region_range: start..region_end,
571        content_range: start..region_end,
572        content: collected,
573        yaml_to_host_offsets,
574    })
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn parsed_yaml_regions_include_frontmatter_and_hashpipe_cst_roots() {
583        let input = "---\ntitle: Test\n---\n\n```{r}\n#| echo: false\n1 + 1\n```\n";
584        let config = crate::config::Config {
585            flavor: crate::config::Flavor::Quarto,
586            extensions: crate::config::Extensions::for_flavor(crate::config::Flavor::Quarto),
587            ..Default::default()
588        };
589        let tree = crate::parser::parse(input, Some(config));
590        let regions = collect_parsed_yaml_regions(&tree);
591        assert_eq!(regions.len(), 2);
592        assert!(regions.iter().any(|parsed| {
593            parsed.is_frontmatter() && parsed.root_kind() == Some(YamlAstRootKind::Root)
594        }));
595        assert!(regions.iter().any(|parsed| {
596            parsed.is_hashpipe() && parsed.root_kind() == Some(YamlAstRootKind::Root)
597        }));
598    }
599
600    #[test]
601    fn parsed_yaml_region_maps_parse_error_to_host_offset() {
602        let input = "```{r}\n#| echo: [\n1 + 1\n```\n";
603        let config = crate::config::Config {
604            flavor: crate::config::Flavor::Quarto,
605            extensions: crate::config::Extensions::for_flavor(crate::config::Flavor::Quarto),
606            ..Default::default()
607        };
608        let tree = crate::parser::parse(input, Some(config));
609        let parsed = collect_parsed_yaml_regions(&tree);
610        let hashpipe = parsed
611            .iter()
612            .find(|region| region.is_hashpipe())
613            .expect("hashpipe region");
614        let host_offset = hashpipe
615            .parse_error_host_offset()
616            .expect("expected parse error host offset");
617        let expected = input.find('[').expect("expected '[' in input");
618        assert_eq!(host_offset, expected);
619    }
620
621    #[test]
622    fn yaml_ast_root_reports_document_shape() {
623        let input = "---\ntitle: Test\n---\n";
624        let tree = crate::parser::parse(input, None);
625        let parsed = collect_parsed_frontmatter_region(&tree).expect("frontmatter");
626        let root = parsed.root().expect("yaml root");
627        assert_eq!(root.document_count(), 1);
628        assert_eq!(root.first_document_kind(), Some(YamlDocumentKind::BlockMap));
629    }
630
631    #[test]
632    fn embedded_yaml_cst_attaches_to_frontmatter_and_hashpipe_hosts() {
633        let input = "---\ntitle: Test\n---\n\n```{r}\n#| echo: false\nx <- 1\n```\n";
634        let config = crate::config::Config {
635            flavor: crate::config::Flavor::Quarto,
636            extensions: crate::config::Extensions::for_flavor(crate::config::Flavor::Quarto),
637            ..Default::default()
638        };
639        let tree = crate::parser::parse(input, Some(config));
640        let embedded = collect_embedded_yaml_cst(&tree);
641        assert_eq!(embedded.len(), 2);
642        assert!(
643            embedded
644                .iter()
645                .any(|item| item.frontmatter_host().is_some())
646        );
647        assert!(embedded.iter().any(|item| item.hashpipe_host().is_some()));
648    }
649
650    #[test]
651    fn embedded_yaml_cst_exposes_frontmatter_and_hashpipe_payloads() {
652        let input = "---\ntitle: Test\n---\n\n```{r}\n#| fig-cap: |\n#|   A caption\nx <- 1\n```\n";
653        let config = crate::config::Config {
654            flavor: crate::config::Flavor::Quarto,
655            extensions: crate::config::Extensions::for_flavor(crate::config::Flavor::Quarto),
656            ..Default::default()
657        };
658        let tree = crate::parser::parse(input, Some(config));
659        let embedded = collect_embedded_yaml_cst(&tree);
660        assert_eq!(embedded.len(), 2);
661
662        let frontmatter = embedded
663            .iter()
664            .find(|item| item.frontmatter_host().is_some())
665            .expect("frontmatter embedding");
666        assert!(frontmatter.parsed().is_valid());
667        assert_eq!(
668            frontmatter.parsed().document_shape_summary().as_deref(),
669            Some("Root docs=1 first=BlockMap")
670        );
671
672        let hashpipe = embedded
673            .iter()
674            .find(|item| item.hashpipe_host().is_some())
675            .expect("hashpipe embedding");
676        assert!(hashpipe.parsed().is_valid());
677        assert!(hashpipe.parsed().to_region().content.contains("fig-cap: |"));
678    }
679
680    #[test]
681    fn embedded_frontmatter_query_returns_typed_host_wrapper() {
682        let input = "---\ntitle: Test\n---\n\nBody\n";
683        let tree = crate::parser::parse(input, None);
684        let embedded = collect_embedded_frontmatter_yaml_cst(&tree).expect("frontmatter embedding");
685        let _host = embedded.frontmatter_host().expect("frontmatter host");
686        assert!(embedded.hashpipe_host().is_none());
687    }
688
689    #[test]
690    fn yaml_offset_map_includes_eof_position() {
691        let input = "---\ntitle: Test\n---\n";
692        let tree = crate::parser::parse(input, None);
693        let parsed = collect_parsed_frontmatter_region(&tree).expect("frontmatter");
694        let eof_yaml_offset = parsed.content().len();
695        assert_eq!(
696            parsed.host_offset_for_yaml_offset(eof_yaml_offset),
697            Some(parsed.content_range().end)
698        );
699    }
700}