mdwright_format/format/
semantic.rs1use mdwright_document::{ParseError, ParseOptions, markdown_signature};
8
9pub fn semantically_equivalent(source: &str, formatted: &str) -> Result<bool, ParseError> {
16 semantically_equivalent_with_options(source, formatted, ParseOptions::default())
17}
18
19pub(crate) fn semantically_equivalent_with_options(
20 source: &str,
21 formatted: &str,
22 parse_options: ParseOptions,
23) -> Result<bool, ParseError> {
24 Ok(markdown_signature(source, parse_options)? == markdown_signature(formatted, parse_options)?)
25}
26
27pub fn first_divergence(source: &str, formatted: &str) -> Result<Option<String>, ParseError> {
35 first_divergence_with_options(source, formatted, ParseOptions::default())
36}
37
38pub(crate) fn first_divergence_with_options(
39 source: &str,
40 formatted: &str,
41 parse_options: ParseOptions,
42) -> Result<Option<String>, ParseError> {
43 let source_sig = markdown_signature(source, parse_options)?;
44 let formatted_sig = markdown_signature(formatted, parse_options)?;
45 Ok(source_sig.first_divergence(&formatted_sig))
46}
47
48#[cfg(test)]
49#[allow(clippy::expect_used)]
50mod tests {
51 use super::semantically_equivalent;
52
53 #[test]
54 fn prose_rewrap_inside_paragraph_is_equivalent() {
55 let a = "alpha beta gamma\ndelta epsilon zeta\n";
56 let b = "alpha beta gamma delta\nepsilon zeta\n";
57 assert!(semantically_equivalent(a, b).expect("semantic check parses"));
58 }
59
60 #[test]
61 fn prose_rewrap_inside_blockquote_is_equivalent() {
62 let a = "> alpha beta gamma\n> delta epsilon zeta\n";
63 let b = "> alpha beta gamma delta epsilon\n> zeta\n";
64 assert!(semantically_equivalent(a, b).expect("semantic check parses"));
65 }
66
67 #[test]
68 fn whitespace_change_inside_fenced_code_is_rejected() {
69 let a = "```\nfoo\nbar\n```\n";
70 let b = "```\nfoo bar\n```\n";
71 assert!(!semantically_equivalent(a, b).expect("semantic check parses"));
72 }
73
74 #[test]
75 fn whitespace_change_inside_inline_code_is_rejected() {
76 let a = "see `x y` here\n";
77 let b = "see `x y` here\n";
78 assert!(!semantically_equivalent(a, b).expect("semantic check parses"));
79 }
80
81 #[test]
82 fn dropped_emphasis_is_rejected() {
83 let a = "foo *bar* baz\n";
84 let b = "foo bar baz\n";
85 assert!(!semantically_equivalent(a, b).expect("semantic check parses"));
86 }
87
88 #[test]
89 fn link_target_change_is_rejected() {
90 let a = "[label](https://a.example)\n";
91 let b = "[label](https://b.example)\n";
92 assert!(!semantically_equivalent(a, b).expect("semantic check parses"));
93 }
94
95 #[test]
96 fn link_text_rewrap_is_equivalent() {
97 let a = "[label one\ntwo](https://x.example)\n";
98 let b = "[label one two](https://x.example)\n";
99 assert!(semantically_equivalent(a, b).expect("semantic check parses"));
100 }
101
102 #[test]
103 fn table_cell_whitespace_rewrap_is_equivalent() {
104 let a = "| a | b |\n|---|---|\n| x | y |\n";
105 let b = "| a | b |\n| --- | --- |\n| x | y |\n";
106 assert!(semantically_equivalent(a, b).expect("semantic check parses"));
107 }
108
109 #[test]
110 fn dropped_heading_level_is_rejected() {
111 let a = "## foo\n";
112 let b = "### foo\n";
113 assert!(!semantically_equivalent(a, b).expect("semantic check parses"));
114 }
115}