#![allow(
clippy::wildcard_imports,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::missing_panics_doc,
clippy::needless_pass_by_value,
clippy::too_many_lines,
reason = "proptest's prelude is its conventional import; invariant assertions and the proptest! macro necessarily panic on failure, and generator combinators move owned strings by value"
)]
use proptest::prelude::*;
use crate::block::{self, Tree};
use crate::fm;
use crate::html::{self, HtmlTag};
use crate::invariants::{
Edit, assert_block_wellformed, assert_edit_sequence_stable, assert_frontmatter_scalar_fidelity,
assert_html_tag_in_bounds, assert_inline_resource_fidelity, assert_line_index_agrees,
assert_structural_invariants, assert_tree_wellformed, collect_scalars, detect_frontmatter,
};
use crate::line_index::LineIndex;
use crate::{inline, json, toml, yaml};
fn proptest_cases() -> u32 {
std::env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(256)
}
fn config() -> ProptestConfig {
ProptestConfig::with_cases(proptest_cases())
}
fn parse_full(source: &str) -> Tree {
let (fm_block, fm_syntax) = detect_frontmatter(source);
let fm_span = fm_block.as_ref().map(|b| b.span);
let fm_entries = fm_block.as_ref().map(|b| b.entries.as_slice());
block::parse_tree_with_entries(source, fm_span, fm_syntax, fm_entries)
}
fn arbitrary_string(max: usize) -> impl Strategy<Value = String> {
proptest::collection::vec(proptest::char::any(), 0..max).prop_map(|cs| cs.into_iter().collect())
}
fn inline_char() -> impl Strategy<Value = char> {
prop_oneof![
6 => (0x20u8..0x7fu8).prop_map(char::from),
1 => prop_oneof![
Just('é'), Just('ü'), Just('ñ'), Just('日'), Just('語'),
Just('🎉'), Just('—'), Just('•'), Just('𝄞'),
],
]
}
fn inline_text(max: usize) -> impl Strategy<Value = String> {
proptest::collection::vec(inline_char(), 0..max).prop_map(|cs| cs.into_iter().collect())
}
fn heading_fragment() -> impl Strategy<Value = String> {
(1usize..=6, inline_text(24))
.prop_map(|(level, text)| format!("{} {text}\n", "#".repeat(level)))
}
fn thematic_break_fragment() -> impl Strategy<Value = String> {
prop_oneof![Just("---\n"), Just("***\n"), Just("___\n"), Just("- - -\n")].prop_map(String::from)
}
fn setext_fragment() -> impl Strategy<Value = String> {
(inline_text(20), prop_oneof![Just("="), Just("-")])
.prop_map(|(text, rule)| format!("{text}\n{}\n", rule.repeat(3)))
}
fn list_item_fragment() -> impl Strategy<Value = String> {
(
prop_oneof![Just("- "), Just("* "), Just("+ "), Just("1. "), Just("2) ")],
inline_text(24),
)
.prop_map(|(marker, text)| format!("{marker}{text}\n"))
}
fn task_item_fragment() -> impl Strategy<Value = String> {
(
prop_oneof![Just("[ ]"), Just("[x]"), Just("[X]")],
inline_text(20),
)
.prop_map(|(box_, text)| format!("- {box_} {text}\n"))
}
fn code_fence_fragment() -> impl Strategy<Value = String> {
(inline_text(8), inline_text(30)).prop_map(|(lang, body)| format!("```{lang}\n{body}\n```\n"))
}
fn indented_code_fragment() -> impl Strategy<Value = String> {
inline_text(24).prop_map(|text| format!(" {text}\n"))
}
fn blockquote_fragment() -> impl Strategy<Value = String> {
(1usize..=3, inline_text(24))
.prop_map(|(depth, text)| format!("{}{text}\n", "> ".repeat(depth)))
}
fn lazy_quote_fragment() -> impl Strategy<Value = String> {
(inline_text(16), inline_text(16)).prop_map(|(first, lazy)| format!("> {first}\n{lazy}\n"))
}
fn table_fragment() -> impl Strategy<Value = String> {
(
inline_text(8),
inline_text(8),
inline_text(8),
inline_text(8),
)
.prop_map(|(a, b, c, d)| format!("| {a} | {b} |\n| --- | --- |\n| {c} | {d} |\n"))
}
fn paragraph_fragment() -> impl Strategy<Value = String> {
inline_text(48).prop_map(|text| format!("{text}\n"))
}
fn blank_fragment() -> impl Strategy<Value = String> {
prop_oneof![Just("\n"), Just(" \n"), Just("\t\n")].prop_map(String::from)
}
fn markdown_fragment() -> impl Strategy<Value = String> {
prop_oneof![
heading_fragment(),
setext_fragment(),
thematic_break_fragment(),
list_item_fragment(),
task_item_fragment(),
code_fence_fragment(),
indented_code_fragment(),
blockquote_fragment(),
lazy_quote_fragment(),
table_fragment(),
paragraph_fragment(),
blank_fragment(),
html_tag_fragment(),
link_fragment(),
]
}
fn markdown_document() -> impl Strategy<Value = String> {
proptest::collection::vec(markdown_fragment(), 0..25).prop_map(|frags| frags.concat())
}
fn url_text() -> impl Strategy<Value = String> {
prop_oneof![
Just("./foo.md".to_string()),
Just("../bar/baz.md#frag".to_string()),
Just("https://example.com/x?y=1".to_string()),
Just("mailto:a@b.com".to_string()),
Just("#section".to_string()),
Just("image.png".to_string()),
inline_text(18),
]
}
fn link_fragment() -> impl Strategy<Value = String> {
prop_oneof\n")),
(inline_text(12), url_text(), inline_text(10))
.prop_map(|(t, u, title)| format!("[{t}]({u} \"{title}\")\n")),
(inline_text(12), inline_text(8)).prop_map(|(t, r)| format!("[{t}][{r}]\n")),
(inline_text(8), url_text()).prop_map(|(label, u)| format!("[{label}]: {u}\n")),
(inline_text(8), url_text(), inline_text(8))
.prop_map(|(label, u, title)| format!("[{label}]: {u} \"{title}\"\n")),
url_text().prop_map(|u| format!("<{u}>\n")),
]
}
fn tag_name() -> impl Strategy<Value = String> {
prop_oneof![
Just("div"),
Just("span"),
Just("br"),
Just("img"),
Just("details"),
Just("a"),
Just("DIV"),
Just("xyz"),
Just(""),
Just("1bad"),
Just("p"),
]
.prop_map(String::from)
}
fn attr_text() -> impl Strategy<Value = String> {
prop_oneof![
Just(String::new()),
Just(" class=\"warning\"".to_string()),
Just(" data-n=1".to_string()),
Just(" disabled".to_string()),
Just(" id='x' title=\"t\"".to_string()),
inline_text(14).prop_map(|s| format!(" {s}")),
]
}
fn html_tag_string() -> impl Strategy<Value = String> {
prop_oneof![
(tag_name(), attr_text()).prop_map(|(n, a)| format!("<{n}{a}>")),
(tag_name(), attr_text()).prop_map(|(n, a)| format!("<{n}{a}/>")),
tag_name().prop_map(|n| format!("</{n}>")),
inline_text(12).prop_map(|c| format!("<!--{c}-->")),
inline_text(12).prop_map(|s| format!("<{s}")), tag_name().prop_map(|n| format!("<{n}")), ]
}
fn html_tag_fragment() -> impl Strategy<Value = String> {
html_tag_string().prop_map(|tag| format!("{tag}\n"))
}
fn nested_quotes() -> impl Strategy<Value = String> {
(1usize..16, inline_text(12))
.prop_map(|(depth, text)| format!("{}{text}\n", "> ".repeat(depth)))
}
fn nested_lists() -> impl Strategy<Value = String> {
(1usize..12, inline_text(8)).prop_map(|(depth, text)| {
let mut out = String::new();
for level in 0..depth {
out.push_str(&" ".repeat(level * 2));
out.push_str("- ");
out.push_str(&text);
out.push('\n');
}
out
})
}
fn nested_html() -> impl Strategy<Value = String> {
(1usize..16).prop_map(|depth| {
let mut out = String::new();
for _ in 0..depth {
out.push_str("<div>");
}
out.push_str("\ntext\n");
for _ in 0..depth {
out.push_str("</div>");
}
out.push('\n');
out
})
}
fn nested_document() -> impl Strategy<Value = String> {
prop_oneof![nested_quotes(), nested_lists(), nested_html()]
}
fn fm_key() -> impl Strategy<Value = String> {
prop_oneof![
Just("title"),
Just("tags"),
Just("author"),
Just("date"),
Just("x")
]
.prop_map(String::from)
}
fn predicate_name() -> impl Strategy<Value = String> {
prop_oneof![
Just("referenced_by"),
Just("superseded_by"),
Just("implemented_by"),
Just("weird_pred"),
]
.prop_map(String::from)
}
fn path_text() -> impl Strategy<Value = String> {
prop_oneof![Just("../README.md"), Just("foo/bar.md"), Just("a.md")].prop_map(String::from)
}
fn yaml_backlinks_block() -> impl Strategy<Value = String> {
proptest::collection::vec(
(
predicate_name(),
proptest::collection::vec(path_text(), 1..4),
),
1..4,
)
.prop_map(|preds| {
let mut out = String::from("backlinks:\n");
for (pred, paths) in preds {
out.push_str(" ");
out.push_str(&pred);
out.push_str(":\n");
for path in paths {
out.push_str(" - ");
out.push_str(&path);
out.push('\n');
}
}
out
})
}
fn yaml_frontmatter() -> impl Strategy<Value = String> {
(
proptest::collection::vec((fm_key(), inline_text(12)), 0..4),
proptest::option::of(yaml_backlinks_block()),
inline_text(20),
)
.prop_map(|(kvs, backlinks, body)| {
let mut out = String::from("---\n");
for (k, v) in kvs {
out.push_str(&k);
out.push_str(": ");
out.push_str(&v);
out.push('\n');
}
if let Some(block) = backlinks {
out.push_str(&block);
}
out.push_str("---\n");
out.push_str(&body);
out.push('\n');
out
})
}
fn toml_frontmatter() -> impl Strategy<Value = String> {
(
proptest::collection::vec((fm_key(), inline_text(10)), 0..4),
inline_text(20),
)
.prop_map(|(kvs, body)| {
let mut out = String::from("+++\n");
for (k, v) in kvs {
out.push_str(&k);
out.push_str(" = \"");
out.push_str(&v.replace('"', ""));
out.push_str("\"\n");
}
out.push_str("+++\n");
out.push_str(&body);
out.push('\n');
out
})
}
fn json_frontmatter() -> impl Strategy<Value = String> {
(
proptest::collection::vec((fm_key(), inline_text(8)), 0..4),
inline_text(20),
)
.prop_map(|(kvs, body)| {
let pairs: Vec<String> = kvs
.into_iter()
.map(|(k, v)| format!("\"{k}\": \"{}\"", v.replace(['"', '\\'], "")))
.collect();
format!("{{{}}}\n{body}\n", pairs.join(", "))
})
}
fn fidelity_text() -> impl Strategy<Value = String> {
proptest::collection::vec(
prop_oneof![
6 => (b'a'..=b'z').prop_map(char::from),
1 => prop_oneof![Just('é'), Just('日'), Just('🎉')],
],
1..12,
)
.prop_map(|cs| cs.into_iter().collect())
}
fn multibyte_frontmatter() -> impl Strategy<Value = String> {
(fidelity_text(), fidelity_text(), 0u8..4).prop_map(|(k, v, fmt)| match fmt {
0 => format!("---\n{k}: {v}\n---\n"), 1 => format!("---\n{k}: \"{v}\"\n---\n"), 2 => format!("+++\n\"{k}\" = \"{v}\"\n+++\n"), _ => format!("{{\"{k}\": \"{v}\"}}\n"), })
}
fn line_ending_variant(doc: String, style: u8, bom: bool) -> String {
let body = match style {
0 => doc,
1 => doc.replace('\n', "\r\n"),
2 => doc.replace('\n', "\r"),
_ => {
let mut out = String::new();
for (i, line) in doc.split_inclusive('\n').enumerate() {
if let Some(stripped) = line.strip_suffix('\n') {
out.push_str(stripped);
out.push_str(match i % 3 {
0 => "\n",
1 => "\r\n",
_ => "\r",
});
} else {
out.push_str(line);
}
}
out
}
};
if bom { format!("\u{feff}{body}") } else { body }
}
fn corrupt(source: String, mode: u8, pos: usize) -> String {
fn boundary(s: &str, idx: usize) -> usize {
let mut i = idx.min(s.len());
while i < s.len() && !s.is_char_boundary(i) {
i += 1;
}
i
}
match mode % 5 {
0 => source,
1 => {
let cut = boundary(&source, pos % (source.len() + 1));
source[..cut].to_string()
}
2 => format!("{source}\u{0}\u{fffd}garbage~~~"), 3 => source
.replacen("---", "-", 1)
.replacen("+++", "+", 1)
.replacen('{', "", 1), _ => {
let at = boundary(&source, pos % (source.len() + 1));
let (head, tail) = source.split_at(at);
format!("{head}≈GARBAGE≈{tail}")
}
}
}
fn maybe_corrupt(base: impl Strategy<Value = String>) -> impl Strategy<Value = String> {
(base, any::<u8>(), any::<usize>()).prop_map(|(s, mode, pos)| corrupt(s, mode, pos))
}
fn edit_text() -> impl Strategy<Value = String> {
prop_oneof![
inline_text(20),
Just("```\n".to_string()),
Just("```rust\n".to_string()),
Just("~~~\n".to_string()),
Just("===\n".to_string()),
Just("---\n".to_string()),
Just("\n".to_string()),
Just("[r]: ./target.md\n".to_string()),
Just("[^f]: a footnote\n".to_string()),
Just("<div>\n".to_string()),
Just("</div>\n".to_string()),
Just("> quote\n".to_string()),
Just("café 🎉\u{200b}\n".to_string()),
Just(String::new()),
]
}
fn edit_coord() -> impl Strategy<Value = u32> {
prop_oneof![
8 => 0u32..30,
1 => 100u32..5000,
1 => Just(u32::MAX),
]
}
fn edit() -> impl Strategy<Value = Edit> {
(
edit_coord(),
edit_coord(),
edit_coord(),
edit_coord(),
edit_text(),
)
.prop_map(|(start_line, start_char, end_line, end_char, text)| Edit {
start_line,
start_char,
end_line,
end_char,
text,
})
}
proptest! {
#![proptest_config(config())]
#[test]
fn tree_wellformed_on_random_utf8(source in arbitrary_string(400)) {
assert_tree_wellformed(&parse_full(&source));
}
#[test]
fn parse_tree_wellformed_on_random_utf8(source in arbitrary_string(300)) {
assert_tree_wellformed(&block::parse_tree(&source, None));
}
#[test]
fn tree_wellformed_on_structured_markdown(source in markdown_document()) {
assert_tree_wellformed(&parse_full(&source));
}
#[test]
fn tree_wellformed_on_nested(source in nested_document()) {
assert_tree_wellformed(&parse_full(&source));
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn parse_inlines_is_idempotent(source in arbitrary_string(300)) {
let mut tree = block::parse_tree(&source, None);
let nodes_before = tree.len();
let diags_before = tree.diagnostics().len();
inline::parse_inlines(&mut tree);
prop_assert_eq!(tree.len(), nodes_before, "re-running the inline pass added nodes");
prop_assert_eq!(
tree.diagnostics().len(),
diags_before,
"re-running the inline pass added diagnostics"
);
assert_tree_wellformed(&tree);
}
#[test]
fn link_documents_wellformed(
links in proptest::collection::vec(link_fragment(), 0..12)
) {
assert_tree_wellformed(&parse_full(&links.concat()));
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn yaml_frontmatter_wellformed(source in maybe_corrupt(yaml_frontmatter())) {
if let Some(block) = yaml::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
}
#[test]
fn toml_frontmatter_wellformed(source in maybe_corrupt(toml_frontmatter())) {
if let Some(block) = toml::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
}
#[test]
fn json_frontmatter_wellformed(source in maybe_corrupt(json_frontmatter())) {
if let Some(block) = json::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
}
#[test]
fn frontmatter_parsers_no_panic_on_random(source in arbitrary_string(300)) {
if let Some(block) = yaml::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
if let Some(block) = toml::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
if let Some(block) = json::parse_frontmatter_block(&source) {
assert_block_wellformed(&block, &source);
}
}
#[test]
fn extract_backlinks_no_panic(
source in prop_oneof![
maybe_corrupt(yaml_frontmatter()),
maybe_corrupt(toml_frontmatter()),
maybe_corrupt(json_frontmatter()),
arbitrary_string(200),
]
) {
let block = yaml::parse_frontmatter_block(&source)
.or_else(|| toml::parse_frontmatter_block(&source))
.or_else(|| json::parse_frontmatter_block(&source));
if let Some(block) = block {
let _ = fm::extract_backlinks(&block, &source);
}
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn tree_wellformed_under_any_line_ending(
doc in prop_oneof![
markdown_document(),
yaml_frontmatter(),
toml_frontmatter(),
json_frontmatter(),
],
style in 0u8..4,
bom in any::<bool>(),
) {
let variant = line_ending_variant(doc, style, bom);
assert_tree_wellformed(&parse_full(&variant));
}
#[test]
fn frontmatter_scalar_text_occurs_in_source(
doc in prop_oneof![
multibyte_frontmatter(),
yaml_frontmatter(),
toml_frontmatter(),
json_frontmatter(),
]
) {
let (block, _) = detect_frontmatter(&doc);
if let Some(block) = block {
assert_frontmatter_scalar_fidelity(&block, &doc);
}
}
#[test]
fn inline_resource_text_occurs_in_source(
doc in prop_oneof![
markdown_document(),
proptest::collection::vec(link_fragment(), 1..12).prop_map(|v| v.concat()),
]
) {
assert_inline_resource_fidelity(&parse_full(&doc));
}
#[test]
fn line_index_matches_scalar_conversion(
doc in prop_oneof![
arbitrary_string(60),
(markdown_document(), 0u8..4, any::<bool>())
.prop_map(|(d, style, bom)| line_ending_variant(d, style, bom)),
]
) {
assert_line_index_agrees(&doc, &LineIndex::new(&doc));
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn edit_sequence_preserves_invariants(
base in prop_oneof![
markdown_document(),
(markdown_document(), 0u8..4, any::<bool>())
.prop_map(|(d, style, bom)| line_ending_variant(d, style, bom)),
yaml_frontmatter(),
toml_frontmatter(),
json_frontmatter(),
],
edits in proptest::collection::vec(edit(), 0..8),
) {
assert_edit_sequence_stable(&base, &edits);
}
}
fn structural_reference_fragment() -> impl Strategy<Value = String> {
prop_oneof.\n".to_string()),
Just("Cross to {Design}/spec.md#overview here.\n".to_string()),
Just("O'Brien's notes 'café/résumé.md' moved.\n".to_string()),
Just("Zero\u{200b}width 'a\u{200b}b.md' path.\n".to_string()),
Just("Unbalanced 'quote path.md continues.\n".to_string()),
Just("Possessive's \"don't.md\" tricky.\n".to_string()),
]
}
fn structural_fragment() -> impl Strategy<Value = String> {
prop_oneof![
heading_fragment(),
html_tag_fragment(),
Just("```\nno language fence\n```\n".to_string()),
Just("```rust\nfn main() {}\n```\n".to_string()),
structural_reference_fragment(),
paragraph_fragment(),
blank_fragment(),
]
}
fn structural_document() -> impl Strategy<Value = String> {
(
proptest::option::of(exceptions_frontmatter()),
proptest::collection::vec(structural_fragment(), 0..20),
)
.prop_map(|(fm, frags)| format!("{}{}", fm.unwrap_or_default(), frags.concat()))
}
fn exceptions_frontmatter() -> impl Strategy<Value = String> {
prop_oneof![
Just(
"---\nexceptions:\n bare_paths:\n \"./missing.md\": gone on purpose\n---\n"
.to_string()
),
Just("---\nexceptions:\n stale_references:\n \"old.md\": legacy\n---\n".to_string()),
Just("---\nexceptions:\n bare_paths:\n \"unmatched.md\":\n---\n".to_string()),
]
}
proptest! {
#![proptest_config(config())]
#[test]
fn structural_diagnostics_valid(
source in prop_oneof![
structural_document(),
(structural_document(), 0u8..4, any::<bool>())
.prop_map(|(d, style, bom)| line_ending_variant(d, style, bom)),
arbitrary_string(300),
markdown_document(),
]
) {
assert_structural_invariants(&source);
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn tokenize_tag_no_panic(
text in prop_oneof![html_tag_string(), arbitrary_string(120)]
) {
if let Some(tag) = html::tokenize_tag(&text, 0) {
assert_html_tag_in_bounds(&tag, &text);
}
}
#[test]
fn tokenize_tag_with_base_offset(text in html_tag_string(), base in 0usize..64) {
if let Some(HtmlTag::Open { attrs, .. }) = html::tokenize_tag(&text, base) {
for attr in attrs {
prop_assert!(
attr.name_span.start >= base,
"attribute name span start {} should be at or after base {base}",
attr.name_span.start
);
}
}
}
}
#[test]
fn wellformed_on_known_tricky_inputs() {
let cases = [
"",
"\n\n\n",
"# heading\n\nparagraph\n",
"> lazy\ncontinuation line\n",
"> > > deep\nlazy\n",
"- a\n - b\n - c\n",
"```\nunclosed code\n",
"<div><div>\ntext\n</div>\n",
"| a | b |\n| --- | --- |\n| c |\n",
"日本語の見出し\n===\n",
"---\ntitle: 値\nbacklinks:\n referenced_by:\n - ../x.md\n---\nbody 🎉\n",
"+++\ntitle = \"t\"\n+++\nbody\n",
"{\"backlinks\": {\"referenced_by\": [\"a.md\"]}}\nbody\n",
"[text](./a.md \"references\") and 🎉 [ref][r]\n\n[r]: ./b.md\n",
"\u{feff}# BOM heading\n",
];
for case in cases {
assert_tree_wellformed(&parse_full(case));
}
}
#[test]
fn frontmatter_blocks_wellformed_on_known_inputs() {
let yaml = "---\ntitle: x\n---\nbody\n";
if let Some(block) = yaml::parse_frontmatter_block(yaml) {
assert_block_wellformed(&block, yaml);
let links = fm::extract_backlinks(&block, yaml);
assert!(links.is_empty(), "no backlinks expected in {yaml:?}");
}
let with_backlinks = "---\nbacklinks:\n referenced_by:\n - a.md\n - b.md\n---\n";
let block =
yaml::parse_frontmatter_block(with_backlinks).expect("frontmatter block should parse");
assert_block_wellformed(&block, with_backlinks);
let links = fm::extract_backlinks(&block, with_backlinks);
assert_eq!(
links.get("referenced_by").map(Vec::len),
Some(2),
"expected two referenced_by paths"
);
}
#[test]
fn multibyte_frontmatter_text_not_corrupted() {
let cases = [
"+++\n\"日本語\" = \"café 🎉\"\n+++\n",
"{\"日本語\": \"café 🎉\"}\n",
"---\n日本語: café 🎉\n---\n",
];
for case in cases {
let (block, _) = detect_frontmatter(case);
let block = block.expect("frontmatter should parse");
for sc in collect_scalars(&block) {
let sliced = &case[sc.span.start..sc.span.end];
assert!(
sliced.contains(sc.text.as_str()),
"resolved scalar {:?} absent from source slice {:?} in {case:?}",
sc.text,
sliced
);
}
}
}
#[test]
fn frontmatter_and_body_survive_mixed_line_endings() {
let docs = [
"---\ntitle: a\nx: b\n---\nbody\n",
"+++\ntitle = \"a\"\nx = \"b\"\n+++\nbody\n",
"{\"a\": \"1\", \"b\": \"2\"}\nbody\n",
"# Heading\n\nA paragraph.\n\n- one\n- two\n",
];
for doc in docs {
for style in 0u8..4 {
for bom in [false, true] {
let variant = line_ending_variant((*doc).to_string(), style, bom);
assert_tree_wellformed(&parse_full(&variant));
}
}
}
}
#[test]
fn flow_collection_recovery_terminates() {
let yaml_cases = [
"---\nx: {a]\n---\n",
"---\nx: {]}\n---\n",
"---\ntitle: { ! \ndate: swZ9U]JF\n---\nbody\n",
"---\nx: [}]\n---\n",
];
for case in yaml_cases {
if let Some(block) = yaml::parse_frontmatter_block(case) {
assert_block_wellformed(&block, case);
let _ = fm::extract_backlinks(&block, case);
}
}
let toml_cases = ["+++\nx = [}]\n+++\n", "+++\nx = [ } , 1]\n+++\n"];
for case in toml_cases {
if let Some(block) = toml::parse_frontmatter_block(case) {
assert_block_wellformed(&block, case);
let _ = fm::extract_backlinks(&block, case);
}
}
let json_cases = [
"{\"tags\": ]}\n",
"{\"k\":[}]}\n",
"{\"tags\":≈GARBAGE≈ \"]######\"}\nbody\n",
];
for case in json_cases {
if let Some(block) = json::parse_frontmatter_block(case) {
assert_block_wellformed(&block, case);
let _ = fm::extract_backlinks(&block, case);
}
}
}
#[test]
fn structural_diagnostics_valid_on_known_inputs() {
let cases = [
"",
"# Heading\n\nSee ./docs/guide.md here.\n",
"It's the team's 'tasks/today.md' file.\n",
"O'Brien's \"don't.md\" and café/résumé.md paths.\n",
"Zero\u{200b}width 'a\u{200b}b.md' reference.\n",
"Unbalanced 'quote that never closes.md\n",
"Cross to {Design}/spec.md#overview here.\n",
"Dangling [link](./missing.md#frag).\n",
"```\nfence without a language\n```\n",
"<div>raw <span>html</span></div>\n",
"\u{feff}# BOM\r\nWith CRLF and a 'path.md' ref.\r\n",
"---\nexceptions:\n bare_paths:\n \"./missing.md\": gone\n---\nSee ./missing.md\n",
"---\nexceptions:\n stale_references:\n \"old.md\": legacy\n---\nbody\n",
];
for case in cases {
assert_structural_invariants(case);
}
}
#[test]
fn edit_sequences_cover_cascade_classes() {
let eof_insert = |text: &str| Edit {
start_line: 9999,
start_char: 0,
end_line: 9999,
end_char: 0,
text: text.to_string(),
};
assert_edit_sequence_stable("```\nlet x = 1;\n", &[eof_insert("```\nafter\n")]);
assert_edit_sequence_stable(
"Title\nbody\n",
&[Edit {
start_line: 1,
start_char: 0,
end_line: 1,
end_char: 0,
text: "===\n".to_string(),
}],
);
assert_edit_sequence_stable("[ref][r]\n\nbody\n", &[eof_insert("[r]: ./x.md\n")]);
assert_edit_sequence_stable("text[^f]\n", &[eof_insert("[^f]: a footnote\n")]);
assert_edit_sequence_stable(
"see [link](./a.md \"references\") here\n",
&[Edit {
start_line: 0,
start_char: 4,
end_line: 0,
end_char: 31,
text: String::new(),
}],
);
assert_edit_sequence_stable(
"# Heading\n",
&[
eof_insert("```\n"),
eof_insert("café 🎉\u{200b}\n"),
eof_insert("```\n"),
],
);
}