dprint_plugin_markdown/
format_text.rs

1use anyhow::bail;
2use anyhow::Result;
3use dprint_core::configuration::resolve_new_line_kind;
4use dprint_core::formatting::*;
5
6use super::configuration::Configuration;
7use super::generation::file_has_ignore_file_directive;
8use super::generation::generate;
9use super::generation::parse_cmark_ast;
10use super::generation::strip_metadata_header;
11use super::generation::Context;
12
13/// Formats a file.
14///
15/// Returns the file text or an error when it failed to parse.
16pub fn format_text(
17  file_text: &str,
18  config: &Configuration,
19  format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
20) -> Result<Option<String>> {
21  let result = format_text_inner(file_text, config, format_code_block_text)?;
22
23  match result {
24    Some(result) if result == file_text => Ok(None),
25    Some(result) => Ok(Some(result)),
26    None => Ok(None),
27  }
28}
29
30fn format_text_inner(
31  file_text: &str,
32  config: &Configuration,
33  format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
34) -> Result<Option<String>> {
35  let file_text = strip_bom(file_text);
36  let (source_file, markdown_text) = match parse_source_file(file_text, config)? {
37    ParseFileResult::IgnoreFile => return Ok(None),
38    ParseFileResult::SourceFile(file) => file,
39  };
40
41  Ok(Some(dprint_core::formatting::format(
42    || {
43      let mut context = Context::new(markdown_text, config, format_code_block_text);
44      #[allow(clippy::let_and_return)]
45      let print_items = generate(&source_file.into(), &mut context);
46      // eprintln!("{}", print_items.get_as_text());
47      print_items
48    },
49    config_to_print_options(file_text, config),
50  )))
51}
52
53#[cfg(feature = "tracing")]
54pub fn trace_file(
55  file_text: &str,
56  config: &Configuration,
57  format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
58) -> dprint_core::formatting::TracingResult {
59  let (source_file, markdown_text) = match parse_source_file(file_text, config).unwrap() {
60    ParseFileResult::IgnoreFile => panic!("Cannot trace file because it has an ignore file comment."),
61    ParseFileResult::SourceFile(file) => file,
62  };
63  dprint_core::formatting::trace_printing(
64    || {
65      let mut context = Context::new(markdown_text, config, format_code_block_text);
66      let print_items = generate(&source_file.into(), &mut context);
67      // eprintln!("{}", print_items.get_as_text());
68      print_items
69    },
70    config_to_print_options(file_text, config),
71  )
72}
73
74fn strip_bom(text: &str) -> &str {
75  text.strip_prefix("\u{FEFF}").unwrap_or(text)
76}
77
78enum ParseFileResult<'a> {
79  IgnoreFile,
80  SourceFile((crate::generation::common::SourceFile, &'a str)),
81}
82
83fn parse_source_file<'a>(file_text: &'a str, config: &Configuration) -> Result<ParseFileResult<'a>> {
84  // check for the presence of a dprint-ignore-file comment before parsing
85  if file_has_ignore_file_directive(strip_metadata_header(file_text), &config.ignore_file_directive) {
86    return Ok(ParseFileResult::IgnoreFile);
87  }
88
89  match parse_cmark_ast(file_text) {
90    Ok(source_file) => Ok(ParseFileResult::SourceFile((source_file, file_text))),
91    Err(error) => bail!(
92      "{}",
93      dprint_core::formatting::utils::string_utils::format_diagnostic(
94        Some((error.range.start, error.range.end)),
95        &error.message,
96        file_text
97      )
98    ),
99  }
100}
101
102fn config_to_print_options(file_text: &str, config: &Configuration) -> PrintOptions {
103  PrintOptions {
104    indent_width: 1, // force
105    max_width: config.line_width,
106    use_tabs: false, // ignore tabs, always use spaces
107    new_line_text: resolve_new_line_kind(file_text, config.new_line_kind),
108  }
109}
110
111#[cfg(test)]
112mod test {
113  use super::*;
114  use crate::configuration::ConfigurationBuilder;
115
116  #[test]
117  fn strips_bom() {
118    for input_text in ["\u{FEFF}#  Title", "\u{FEFF}# Title\n"] {
119      let config = ConfigurationBuilder::new().build();
120      let result = format_text(input_text, &config, |_, _, _| Ok(None)).unwrap();
121      assert_eq!(result, Some("# Title\n".to_string()));
122    }
123  }
124}