readme_sync/
file_docs.rs

1use core::cmp::Ordering;
2use core::ops::Range;
3use std::string::String;
4use std::sync::Arc;
5use std::vec::Vec;
6
7use thiserror::Error;
8
9use crate::{Config, File};
10
11/// Parsed `.rs` file documentation.
12#[derive(Clone, Debug, Eq, Hash, PartialEq)]
13pub struct FileDocs {
14    /// The source file.
15    file: Arc<File>,
16    /// Parsed documentation text.
17    docs: String,
18    /// Text ranges remap from parsed documentation to the original file contents.
19    remap: Vec<TextRemap>,
20}
21
22/// The pair of a source and the corresponding target text remap range.
23#[derive(Clone, Debug, Eq, Hash, PartialEq)]
24pub struct TextRemap {
25    /// Source text range.
26    pub source: Range<usize>,
27    /// Target text range.
28    pub target: Range<usize>,
29}
30
31impl FileDocs {
32    /// Creates file documentations from the specified file with the specified features.
33    pub fn from_file(file: Arc<File>, config: &Config<'_>) -> Result<Self, FileDocsFromFileError> {
34        use crate::build_attr_docs;
35
36        let file_text = file.text();
37        let line_offsets: Vec<_> = file_text
38            .split('\n')
39            .map(|slice| slice.as_ptr() as usize - file_text.as_ptr() as usize)
40            .collect();
41
42        let ast = syn::parse_file(file_text)?;
43        let chunks: Result<Vec<_>, _> = ast
44            .attrs
45            .iter()
46            .map(|attr| build_attr_docs(attr, config))
47            .collect();
48        let chunks = chunks?;
49
50        let (docs, mut remap, _) = chunks.into_iter().flatten().fold(
51            (String::new(), Vec::new(), None),
52            |(text, mut remap, last), item| {
53                let range = item.span.map(|span| {
54                    line_offsets[span.start.line] + span.start.column
55                        ..line_offsets[span.end.line] + span.end.column
56                });
57                if let Some(range) = range.clone() {
58                    remap.push(TextRemap {
59                        source: text.len()..text.len() + item.text.len(),
60                        target: range,
61                    });
62                }
63                (
64                    text + &item.text,
65                    remap,
66                    range.map_or_else(|| last, |range| Some(range.end)),
67                )
68            },
69        );
70
71        remap.sort();
72        Ok(FileDocs { file, docs, remap })
73    }
74
75    /// Returns file file.
76    pub fn file(&self) -> &Arc<File> {
77        &self.file
78    }
79
80    /// Returns file docs.
81    pub fn docs(&self) -> &str {
82        &self.docs
83    }
84
85    /// Returns file remap.
86    pub fn remap(&self) -> &[TextRemap] {
87        &self.remap
88    }
89
90    /// Remaps range from parsed documentation to source file content.
91    pub fn remap_to_file(&self, range: Range<usize>) -> Option<Range<usize>> {
92        let remap_idx = self
93            .remap
94            .binary_search_by(|remap| {
95                if range.start < remap.source.start {
96                    Ordering::Greater
97                } else if range.start < remap.source.end {
98                    Ordering::Equal
99                } else {
100                    Ordering::Less
101                }
102            })
103            .ok()?;
104
105        let remap = &self.remap[remap_idx];
106        Some(remap.target.start..remap.target.end)
107    }
108}
109
110impl PartialOrd for TextRemap {
111    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116impl Ord for TextRemap {
117    fn cmp(&self, other: &Self) -> Ordering {
118        self.source.start.cmp(&other.source.start)
119    }
120}
121
122/// An error which can occur when creating file documentation form a given file.
123#[derive(Clone, Debug, Error)]
124pub enum FileDocsFromFileError {
125    /// File parsing error
126    #[error("File parser error: {0}")]
127    SynError(#[from] syn::Error),
128    /// Attribute or meta parsing error.
129    #[error(transparent)]
130    AttrError(#[from] crate::BuildAttrDocsError),
131}