forc_doc/render/util/format/
docstring.rs

1//! Rendering and formatting for Sway doc attributes.
2use crate::render::util::format::constant::*;
3use comrak::{markdown_to_html, ComrakOptions};
4use std::fmt::Write;
5use sway_core::transform::{AttributeKind, Attributes};
6use sway_lsp::utils::markdown::format_docs;
7
8pub(crate) trait DocStrings {
9    fn to_html_string(&self) -> String;
10    fn to_raw_string(&self) -> String;
11}
12/// Creates an HTML String from [Attributes].
13impl DocStrings for Attributes {
14    fn to_html_string(&self) -> String {
15        let docs = self.to_raw_string();
16
17        let mut options = ComrakOptions::default();
18        options.render.hardbreaks = true;
19        options.extension.strikethrough = true;
20        options.extension.table = true;
21        options.extension.autolink = true;
22        options.extension.superscript = true;
23        options.extension.footnotes = true;
24        options.parse.smart = true;
25        options.parse.default_info_string = Some(SWAY_FILEINE.into());
26        markdown_to_html(&format_docs(&docs), &options)
27    }
28    fn to_raw_string(&self) -> String {
29        let mut docs = String::new();
30        // TODO: Change this logic once https://github.com/FuelLabs/sway/issues/6938 gets implemented.
31        for arg in self
32            .of_kind(AttributeKind::DocComment)
33            .flat_map(|attribute| &attribute.args)
34        {
35            writeln!(docs, "{}", arg.name.as_str())
36                .expect("problem appending `arg.name.as_str()` to `docs` with `writeln` macro.");
37        }
38        docs
39    }
40}
41
42/// Create a docstring preview from raw html attributes.
43///
44/// Returns `None` if there are no attributes.
45pub(crate) fn create_preview(raw_attributes: Option<String>) -> Option<String> {
46    raw_attributes.as_ref().map(|description| {
47        let preview = split_at_markdown_header(description);
48        if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains(CLOSING_PARAGRAPH_TAG) {
49            let closing_tag_index = preview
50                .find(CLOSING_PARAGRAPH_TAG)
51                .expect("closing tag out of range");
52            // We add 1 here to get the index of the char after the closing tag.
53            // This ensures we retain the closing tag and don't break the html.
54            let (preview, _) =
55                preview.split_at(closing_tag_index + CLOSING_PARAGRAPH_TAG.len() + 1);
56            if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains(NEWLINE_CHAR) {
57                let newline_index = preview
58                    .find(NEWLINE_CHAR)
59                    .expect("new line char out of range");
60                preview.split_at(newline_index).0.to_string()
61            } else {
62                preview.to_string()
63            }
64        } else {
65            preview.to_string()
66        }
67    })
68}
69
70/// Checks if some raw html (rendered from markdown) contains a header.
71/// If it does, it splits at the header and returns the slice that preceded it.
72pub(crate) fn split_at_markdown_header(raw_html: &str) -> &str {
73    for header in HTML_HEADERS {
74        if raw_html.contains(header) {
75            let v: Vec<_> = raw_html.split(header).collect();
76            return v.first().expect("expected non-empty &str");
77        }
78        continue;
79    }
80    raw_html
81}