forge_doc/preprocessor/
inheritdoc.rs

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
use super::{Preprocessor, PreprocessorId};
use crate::{
    document::DocumentContent, Comments, Document, ParseItem, ParseSource, PreprocessorOutput,
};
use alloy_primitives::map::HashMap;
use forge_fmt::solang_ext::SafeUnwrap;

/// [`Inheritdoc`] preprocessor ID.
pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc");

/// The inheritdoc preprocessor.
/// Traverses the documents and attempts to find inherited
/// comments for inheritdoc comment tags.
///
/// This preprocessor writes to [Document]'s context.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Inheritdoc;

impl Preprocessor for Inheritdoc {
    fn id(&self) -> PreprocessorId {
        INHERITDOC_ID
    }

    fn preprocess(&self, documents: Vec<Document>) -> Result<Vec<Document>, eyre::Error> {
        for document in documents.iter() {
            if let DocumentContent::Single(ref item) = document.content {
                let context = self.visit_item(item, &documents);
                if !context.is_empty() {
                    document.add_context(self.id(), PreprocessorOutput::Inheritdoc(context));
                }
            }
        }

        Ok(documents)
    }
}

impl Inheritdoc {
    fn visit_item(&self, item: &ParseItem, documents: &Vec<Document>) -> HashMap<String, Comments> {
        let mut context = HashMap::default();

        // Match for the item first.
        let matched = item
            .comments
            .find_inheritdoc_base()
            .and_then(|base| self.try_match_inheritdoc(base, &item.source, documents));
        if let Some((key, comments)) = matched {
            context.insert(key, comments);
        }

        // Match item's children.
        for ch in item.children.iter() {
            let matched = ch
                .comments
                .find_inheritdoc_base()
                .and_then(|base| self.try_match_inheritdoc(base, &ch.source, documents));
            if let Some((key, comments)) = matched {
                context.insert(key, comments);
            }
        }

        context
    }

    fn try_match_inheritdoc(
        &self,
        base: &str,
        source: &ParseSource,
        documents: &Vec<Document>,
    ) -> Option<(String, Comments)> {
        for candidate in documents {
            if let DocumentContent::Single(ref item) = candidate.content {
                if let ParseSource::Contract(ref contract) = item.source {
                    if base == contract.name.safe_unwrap().name {
                        // Not matched for the contract because it's a noop
                        // https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags

                        for children in item.children.iter() {
                            // TODO: improve matching logic
                            if source.ident() == children.source.ident() {
                                let key = format!("{}.{}", base, source.ident());
                                return Some((key, children.comments.clone()))
                            }
                        }
                    }
                }
            }
        }
        None
    }
}