Skip to main content

forge_doc/preprocessor/
inheritdoc.rs

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