diff --git a/mdsf/src/lib.rs b/mdsf/src/lib.rs index acc74bd88..b5564a46c 100644 --- a/mdsf/src/lib.rs +++ b/mdsf/src/lib.rs @@ -100,9 +100,9 @@ pub fn format_file( let is_go = language == "go" || language == "golang"; let (is_snippet, code_snippet, snippet_lines) = if is_go { - parse_go_codeblock(&mut lines) + parse_go_codeblock(&mut lines, &indentation) } else { - parse_generic_codeblock(&mut lines) + parse_generic_codeblock(&mut lines, &indentation) }; if is_snippet { diff --git a/mdsf/src/parser/mod.rs b/mdsf/src/parser/mod.rs index 41fcc286c..466cfcdbe 100644 --- a/mdsf/src/parser/mod.rs +++ b/mdsf/src/parser/mod.rs @@ -5,7 +5,10 @@ use regex::Regex; const GO_TEMPORARY_PACKAGE_NAME: &str = "package mdsfformattertemporarynamespace\n"; #[inline] -pub fn parse_generic_codeblock(lines: &mut Enumerate) -> (bool, String, usize) { +pub fn parse_generic_codeblock( + lines: &mut Enumerate, + indentation: &str, +) -> (bool, String, usize) { let mut code_snippet = String::new(); let mut is_snippet = false; @@ -20,7 +23,7 @@ pub fn parse_generic_codeblock(lines: &mut Enumerate) -> (bool, String, u break; } - code_snippet.push_str(subline); + code_snippet.push_str(dedent_line(subline, indentation)); code_snippet.push(crate::config::LF_NEWLINE_CHAR); } @@ -29,8 +32,11 @@ pub fn parse_generic_codeblock(lines: &mut Enumerate) -> (bool, String, u } #[inline] -pub fn parse_go_codeblock(lines: &mut Enumerate) -> (bool, String, usize) { - let (is_snippet, mut code_snippet, snippet_lines) = parse_generic_codeblock(lines); +pub fn parse_go_codeblock( + lines: &mut Enumerate, + indentation: &str, +) -> (bool, String, usize) { + let (is_snippet, mut code_snippet, snippet_lines) = parse_generic_codeblock(lines, indentation); if is_snippet && !GO_PACKAGE_RE.is_match(&code_snippet) { code_snippet.insert_str(0, GO_TEMPORARY_PACKAGE_NAME); @@ -52,6 +58,16 @@ pub fn remove_go_package(snippet: String) -> String { } } +#[inline] +fn dedent_line<'a>(line: &'a str, indentation: &str) -> &'a str { + if indentation.is_empty() { + line + } else { + line.strip_prefix(indentation) + .unwrap_or_else(|| line.trim_start()) + } +} + #[inline] pub fn indent_codeblock(indentation: &str, snippet: String) -> String { if indentation.is_empty() { @@ -59,12 +75,68 @@ pub fn indent_codeblock(indentation: &str, snippet: String) -> String { } else { snippet .lines() - .map(|line| format!("{indentation}{line}")) + .map(|line| { + if line.is_empty() { + line.to_owned() + } else { + format!("{indentation}{line}") + } + }) .collect::>() .join(crate::config::Newline::Lf.as_str()) } } +#[cfg(test)] +mod test_parse_generic_codeblock { + use crate::parser::parse_generic_codeblock; + + #[test] + fn it_should_remove_the_codeblock_indentation() { + let input = " a = 1\n\n b = 2\n ```"; + + let (is_snippet, snippet, snippet_lines) = + parse_generic_codeblock(&mut input.lines().enumerate(), " "); + + assert!(is_snippet); + assert_eq!("a = 1\n\nb = 2\n", snippet); + assert_eq!(4, snippet_lines); + } + + #[test] + fn it_should_keep_lines_of_unindented_codeblocks() { + let input = "a = 1\n b = 2\n```"; + + let (is_snippet, snippet, snippet_lines) = + parse_generic_codeblock(&mut input.lines().enumerate(), ""); + + assert!(is_snippet); + assert_eq!("a = 1\n b = 2\n", snippet); + assert_eq!(3, snippet_lines); + } +} + +#[cfg(test)] +mod test_indent_codeblock { + use crate::parser::indent_codeblock; + + #[test] + fn it_should_indent_every_line() { + assert_eq!( + " a = 1\n b = 2", + indent_codeblock(" ", "a = 1\nb = 2".to_owned()) + ); + } + + #[test] + fn it_should_not_indent_empty_lines() { + assert_eq!( + " a = 1\n\n b = 2", + indent_codeblock(" ", "a = 1\n\nb = 2".to_owned()) + ); + } +} + #[cfg(test)] mod test_go_package_re { use crate::parser::GO_PACKAGE_RE;