1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use core::cmp::Ordering;
use core::ops::Range;
use std::string::String;
use std::sync::Arc;
use std::vec::Vec;

#[cfg(all(feature = "syn", feature = "thiserror"))]
use thiserror::Error;

#[cfg(all(feature = "syn", feature = "thiserror"))]
use crate::Config;
use crate::File;

/// Parsed `.rs` file documentation.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct FileDocs {
    /// The source file.
    file: Arc<File>,
    /// Parsed documentation text.
    docs: String,
    /// Text ranges remap from parsed documentation to the original file contents.
    remap: Vec<TextRemap>,
}

/// The pair of a source and the corresponding target text remap range.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TextRemap {
    /// Source text range.
    pub source: Range<usize>,
    /// Target text range.
    pub target: Range<usize>,
}

impl FileDocs {
    /// Creates file documentations from the specified file with the specified features.
    #[cfg(all(feature = "syn", feature = "thiserror"))]
    pub fn from_file(file: Arc<File>, config: &Config<'_>) -> Result<Self, FileDocsFromFileError> {
        use crate::build_attr_docs;

        let file_text = file.text();
        let line_offsets: Vec<_> = file_text
            .split('\n')
            .map(|slice| slice.as_ptr() as usize - file_text.as_ptr() as usize)
            .collect();

        let ast = syn::parse_file(file_text)?;
        let chunks: Result<Vec<_>, _> = ast
            .attrs
            .iter()
            .map(|attr| build_attr_docs(attr, config))
            .collect();
        let chunks = chunks?;

        let (docs, mut remap, _) = chunks.into_iter().flatten().fold(
            (String::new(), Vec::new(), None),
            |(text, mut remap, last), item| {
                let range = item.span.map(|span| {
                    line_offsets[span.start.line] + span.start.column
                        ..line_offsets[span.end.line] + span.end.column
                });
                if let Some(range) = range.clone() {
                    remap.push(TextRemap {
                        source: text.len()..text.len() + item.text.len(),
                        target: range,
                    });
                }
                (
                    text + &item.text,
                    remap,
                    range.map_or_else(|| last, |range| Some(range.end)),
                )
            },
        );

        remap.sort();
        Ok(FileDocs { file, docs, remap })
    }

    /// Returns file file.
    pub fn file(&self) -> &Arc<File> {
        &self.file
    }

    /// Returns file docs.
    pub fn docs(&self) -> &str {
        &self.docs
    }

    /// Returns file remap.
    pub fn remap(&self) -> &[TextRemap] {
        &self.remap
    }

    /// Remaps range from parsed documentation to source file content.
    pub fn remap_to_file(&self, range: Range<usize>) -> Option<Range<usize>> {
        let remap_idx = self
            .remap
            .binary_search_by(|remap| {
                if range.start < remap.source.start {
                    Ordering::Greater
                } else if range.start < remap.source.end {
                    Ordering::Equal
                } else {
                    Ordering::Less
                }
            })
            .ok()?;

        let remap = &self.remap[remap_idx];
        Some(remap.target.start..remap.target.end)
    }
}

impl PartialOrd for TextRemap {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for TextRemap {
    fn cmp(&self, other: &Self) -> Ordering {
        self.source.start.cmp(&other.source.start)
    }
}

/// An error which can occur when creating file documentation form a given file.
#[cfg(all(feature = "syn", feature = "thiserror"))]
#[derive(Clone, Debug, Error)]
pub enum FileDocsFromFileError {
    /// File parsing error
    #[error("File parser error: {0}")]
    SynError(#[from] syn::Error),
    /// Attribute or meta parsing error.
    #[error(transparent)]
    AttrError(#[from] crate::BuildAttrDocsError),
}