use proptest::prelude::*;
use super::idempotence_harness::{
BREAK_SPELLINGS,
TABLE_DELIMITER_ROWS,
adjacency_strategy,
prose_strategy,
};
pub const WRAP_WIDTH: usize = 80;
pub const MIN_FENCE_RUN: usize = 3;
pub const MAX_FENCE_RUN: usize = 5;
const HARD_BREAK_MARKERS: &[&str] = &[" ", "\\"];
fn code_span_strategy() -> impl Strategy<Value = String> {
proptest::collection::vec("[a-z]{2,6}", 1..=3)
.prop_map(|segments| format!("`{}`", segments.join("/")))
}
fn tail_strategy() -> impl Strategy<Value = String> {
prop_oneof![
2 => Just(String::new()),
3 => prose_strategy().prop_map(|prose| format!(" {prose}")),
3 => prose_strategy().prop_map(|prose| format!(" ({prose})")),
3 => code_span_strategy().prop_map(|span| format!(" ({span})")),
]
}
pub fn ordered_marker_strategy() -> impl Strategy<Value = String> {
(1_u32..=999, 0_usize..=2)
.prop_map(|(number, depth)| format!("{}{number}. ", " ".repeat(depth)))
}
pub fn nested_ordered_list_strategy() -> impl Strategy<Value = String> {
(1_u32..=9, 1_u32..=999, prose_strategy(), prose_strategy())
.prop_map(|(parent, child, head, tail)| format!("{parent}. {head}\n {child}. {tail}"))
}
fn prefixed_line_strategy() -> impl Strategy<Value = String> {
let marker = prop_oneof![
3 => Just("- ".to_string()),
1 => Just("- [ ] ".to_string()),
2 => Just("1. ".to_string()),
2 => ordered_marker_strategy(),
2 => Just("> ".to_string()),
1 => Just("[^1]: ".to_string()),
1 => Just(" - ".to_string()),
];
(marker, prose_strategy(), tail_strategy())
.prop_map(|(marker, prose, tail)| format!("{marker}{prose}{tail}"))
}
fn continuation_strategy() -> impl Strategy<Value = String> {
prop_oneof![
3 => prose_strategy().prop_map(|prose| format!(" {prose}")),
2 => prose_strategy(),
1 => prose_strategy().prop_map(|prose| format!(" {prose}")),
1 => prose_strategy().prop_map(|prose| format!("> {prose}")),
1 => Just(String::new()),
]
}
fn prefixed_block_strategy() -> impl Strategy<Value = String> {
(
prefixed_line_strategy(),
prop::option::of(continuation_strategy()),
)
.prop_map(|(line, continuation)| match continuation {
Some(continuation) => format!("{line}\n{continuation}"),
None => line,
})
}
pub fn overlong_code_span_block_strategy() -> impl Strategy<Value = String> {
(
prose_strategy(),
code_span_strategy(),
continuation_strategy(),
)
.prop_map(|(prose, span, continuation)| {
let mut line = format!("- {prose}");
while line.len() + span.len() + 3 <= WRAP_WIDTH {
line.push_str(" and more prose");
}
format!("{line} ({span})\n{continuation}")
})
}
#[derive(Clone, Copy, Debug)]
struct FenceShape {
marker: char,
opener_len: usize,
interior_len: Option<usize>,
}
fn fence_shape_strategy() -> impl Strategy<Value = FenceShape> {
let opener = (
prop_oneof![Just('`'), Just('~')],
MIN_FENCE_RUN..=MAX_FENCE_RUN,
);
opener.prop_flat_map(|(marker, opener_len)| {
let interior_len = if opener_len > MIN_FENCE_RUN {
prop::option::of(MIN_FENCE_RUN..opener_len).boxed()
} else {
Just(None).boxed()
};
interior_len.prop_map(move |interior_len| FenceShape {
marker,
opener_len,
interior_len,
})
})
}
fn fence_interior_strategy(shape: FenceShape) -> impl Strategy<Value = String> {
let other_marker = if shape.marker == '`' { '~' } else { '`' };
let same_marker = shape.marker;
let shorter_run = shape.interior_len.map_or_else(String::new, |len| {
std::iter::repeat_n(same_marker, len).collect()
});
prop_oneof![
4 => prose_strategy(),
1 => Just(String::new()),
3 => (MIN_FENCE_RUN..=MAX_FENCE_RUN)
.prop_map(move |len| std::iter::repeat_n(other_marker, len).collect()),
3 => Just(shorter_run),
]
}
pub fn fenced_block_strategy() -> impl Strategy<Value = String> {
(fence_shape_strategy(), any::<bool>()).prop_flat_map(|(shape, is_unmatched)| {
let interior = prop::collection::vec(fence_interior_strategy(shape), 1..=4);
interior.prop_map(move |interior| {
let opener: String = std::iter::repeat_n(shape.marker, shape.opener_len).collect();
let closer = (!is_unmatched).then(|| opener.clone());
std::iter::once(opener)
.chain(interior)
.chain(closer)
.collect::<Vec<_>>()
.join("\n")
})
})
}
fn single_line_character_strategy() -> impl Strategy<Value = char> {
prop_oneof![
1 => proptest::char::range('\u{0}', '\u{7f}'),
1 => any::<char>(),
]
.prop_map(|character| match character {
'\r' | '\n' => ' ',
other => other,
})
}
pub fn table_cell_strategy() -> impl Strategy<Value = String> {
prop::collection::vec(single_line_character_strategy(), 0..=8).prop_map(|characters| {
characters
.into_iter()
.map(|character| match character {
'|' => "\\|".to_string(),
other => other.to_string(),
})
.collect()
})
}
fn table_row_strategy() -> impl Strategy<Value = String> {
(table_cell_strategy(), table_cell_strategy())
.prop_map(|(left, right)| format!("| {left} | {right} |"))
}
fn table_strategy() -> impl Strategy<Value = String> {
(
proptest::sample::select(TABLE_DELIMITER_ROWS),
table_row_strategy(),
prop::collection::vec(table_row_strategy(), 1..=3),
)
.prop_map(|(delimiter, header, body)| {
std::iter::once(header)
.chain(std::iter::once(delimiter.to_string()))
.chain(body)
.collect::<Vec<_>>()
.join("\n")
})
}
pub fn hard_break_paragraph_strategy() -> impl Strategy<Value = String> {
(
prose_strategy(),
prop::collection::vec(
(
prose_strategy(),
proptest::sample::select(HARD_BREAK_MARKERS),
),
1..=3,
),
)
.prop_map(|(head, tail)| {
std::iter::once(head)
.chain(
tail.into_iter()
.map(|(prose, marker)| format!("{prose}{marker}")),
)
.collect::<Vec<_>>()
.join("\n")
})
}
pub fn overlong_code_span_paragraph_strategy() -> impl Strategy<Value = String> {
(
prose_strategy(),
prose_strategy(),
(WRAP_WIDTH + 1)..=(WRAP_WIDTH * 2),
)
.prop_map(|(head, tail, span_len)| {
let span = "x".repeat(span_len);
format!("{head} `{span}` {tail}")
})
}
fn element_strategy() -> impl Strategy<Value = String> {
prop_oneof![
4 => prose_strategy(),
4 => prefixed_block_strategy(),
2 => nested_ordered_list_strategy(),
2 => proptest::sample::select(BREAK_SPELLINGS).prop_map(str::to_string),
2 => adjacency_strategy()
.prop_map(|(document, _, _)| document.trim_end_matches('\n').to_string()),
2 => fenced_block_strategy(),
2 => crate::bracket_reference_seam_strategy(),
2 => table_strategy(),
2 => hard_break_paragraph_strategy(),
2 => overlong_code_span_paragraph_strategy(),
1 => prose_strategy().prop_map(|title| format!("{title}\n-----")),
1 => Just("[1] and text... here".to_string()),
1 => Just("**bold**`code`".to_string()),
1 => Just(String::new()),
]
}
pub fn document_strategy() -> impl Strategy<Value = String> {
proptest::collection::vec(element_strategy(), 1..=10)
.prop_map(|elements| elements.join("\n") + "\n")
}