forge_doc/preprocessor/
inheritdoc.rs

1use super::{Preprocessor, PreprocessorId};
2use crate::{
3    document::DocumentContent, Comments, Document, ParseItem, ParseSource, PreprocessorOutput,
4};
5use alloy_primitives::map::HashMap;
6use forge_fmt::solang_ext::SafeUnwrap;
7
8/// [`Inheritdoc`] preprocessor ID.
9pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc");
10
11/// The inheritdoc preprocessor.
12/// Traverses the documents and attempts to find inherited
13/// comments for inheritdoc comment tags.
14///
15/// This preprocessor writes to [Document]'s context.
16#[derive(Debug, Default)]
17#[non_exhaustive]
18pub struct Inheritdoc;
19
20impl Preprocessor for Inheritdoc {
21    fn id(&self) -> PreprocessorId {
22        INHERITDOC_ID
23    }
24
25    fn preprocess(&self, documents: Vec<Document>) -> Result<Vec<Document>, eyre::Error> {
26        for document in &documents {
27            if let DocumentContent::Single(ref item) = document.content {
28                let context = self.visit_item(item, &documents);
29                if !context.is_empty() {
30                    document.add_context(self.id(), PreprocessorOutput::Inheritdoc(context));
31                }
32            }
33        }
34
35        Ok(documents)
36    }
37}
38
39impl Inheritdoc {
40    fn visit_item(&self, item: &ParseItem, documents: &Vec<Document>) -> HashMap<String, Comments> {
41        let mut context = HashMap::default();
42
43        // Match for the item first.
44        let matched = item
45            .comments
46            .find_inheritdoc_base()
47            .and_then(|base| self.try_match_inheritdoc(base, &item.source, documents));
48        if let Some((key, comments)) = matched {
49            context.insert(key, comments);
50        }
51
52        // Match item's children.
53        for ch in &item.children {
54            let matched = ch
55                .comments
56                .find_inheritdoc_base()
57                .and_then(|base| self.try_match_inheritdoc(base, &ch.source, documents));
58            if let Some((key, comments)) = matched {
59                context.insert(key, comments);
60            }
61        }
62
63        context
64    }
65
66    fn try_match_inheritdoc(
67        &self,
68        base: &str,
69        source: &ParseSource,
70        documents: &Vec<Document>,
71    ) -> Option<(String, Comments)> {
72        for candidate in documents {
73            if let DocumentContent::Single(ref item) = candidate.content {
74                if let ParseSource::Contract(ref contract) = item.source {
75                    if base == contract.name.safe_unwrap().name {
76                        // Not matched for the contract because it's a noop
77                        // https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags
78
79                        for children in &item.children {
80                            // TODO: improve matching logic
81                            if source.ident() == children.source.ident() {
82                                let key = format!("{}.{}", base, source.ident());
83                                return Some((key, children.comments.clone()))
84                            }
85                        }
86                    }
87                }
88            }
89        }
90        None
91    }
92}