forge_doc/parser/
item.rs

1use crate::{Comments, helpers::function_signature, solang_ext::SafeUnwrap};
2use solang_parser::pt::{
3    ContractDefinition, ContractTy, EnumDefinition, ErrorDefinition, EventDefinition,
4    FunctionDefinition, StructDefinition, TypeDefinition, VariableDefinition,
5};
6use std::ops::Range;
7
8/// The parsed item.
9#[derive(Debug, PartialEq)]
10pub struct ParseItem {
11    /// The parse tree source.
12    pub source: ParseSource,
13    /// Item comments.
14    pub comments: Comments,
15    /// Children items.
16    pub children: Vec<ParseItem>,
17    /// Formatted code string.
18    pub code: String,
19}
20
21/// Defines a method that filters [ParseItem]'s children and returns the source pt token of the
22/// children matching the target variant as well as its comments.
23/// Returns [Option::None] if no children matching the variant are found.
24macro_rules! filter_children_fn {
25    ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => {
26        /// Filter children items for [ParseSource::$variant] variants.
27        $vis fn $name(&self) -> Option<Vec<(&$ret, &Comments, &String)>> {
28            let items = self.children.iter().filter_map(|item| match item.source {
29                ParseSource::$variant(ref inner) => Some((inner, &item.comments, &item.code)),
30                _ => None,
31            });
32            let items = items.collect::<Vec<_>>();
33            if !items.is_empty() {
34                Some(items)
35            } else {
36                None
37            }
38        }
39    };
40}
41
42/// Defines a method that returns [ParseSource] inner element if it matches
43/// the variant
44macro_rules! as_inner_source {
45    ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => {
46        /// Return inner element if it matches $variant.
47        /// If the element doesn't match, returns [None]
48        $vis fn $name(&self) -> Option<&$ret> {
49            match self.source {
50                ParseSource::$variant(ref inner) => Some(inner),
51                _ => None
52            }
53        }
54    };
55}
56
57impl ParseItem {
58    /// Create new instance of [ParseItem].
59    pub fn new(source: ParseSource) -> Self {
60        Self {
61            source,
62            comments: Default::default(),
63            children: Default::default(),
64            code: Default::default(),
65        }
66    }
67
68    /// Set comments on the [ParseItem].
69    pub fn with_comments(mut self, comments: Comments) -> Self {
70        self.comments = comments;
71        self
72    }
73
74    /// Set children on the [ParseItem].
75    pub fn with_children(mut self, children: Vec<Self>) -> Self {
76        self.children = children;
77        self
78    }
79
80    /// Set the source code of this [ParseItem].
81    ///
82    /// The parameter should be the full source file where this parse item originated from.
83    pub fn with_code(mut self, source: &str) -> Self {
84        let mut code = source[self.source.range()].to_string();
85
86        // Special function case, add `;` at the end of definition.
87        if let ParseSource::Function(_) = self.source {
88            code.push(';');
89        }
90
91        // Remove extra indent from source lines.
92        self.code = code
93            .lines()
94            .map(|line| line.strip_prefix("    ").unwrap_or(line))
95            .collect::<Vec<_>>()
96            .join("\n");
97        self
98    }
99
100    /// Format the item's filename.
101    pub fn filename(&self) -> String {
102        let prefix = match self.source {
103            ParseSource::Contract(ref c) => match c.ty {
104                ContractTy::Contract(_) => "contract",
105                ContractTy::Abstract(_) => "abstract",
106                ContractTy::Interface(_) => "interface",
107                ContractTy::Library(_) => "library",
108            },
109            ParseSource::Function(_) => "function",
110            ParseSource::Variable(_) => "variable",
111            ParseSource::Event(_) => "event",
112            ParseSource::Error(_) => "error",
113            ParseSource::Struct(_) => "struct",
114            ParseSource::Enum(_) => "enum",
115            ParseSource::Type(_) => "type",
116        };
117        let ident = self.source.ident();
118        format!("{prefix}.{ident}.md")
119    }
120
121    filter_children_fn!(pub fn variables(&self, Variable) -> VariableDefinition);
122    filter_children_fn!(pub fn functions(&self, Function) -> FunctionDefinition);
123    filter_children_fn!(pub fn events(&self, Event) -> EventDefinition);
124    filter_children_fn!(pub fn errors(&self, Error) -> ErrorDefinition);
125    filter_children_fn!(pub fn structs(&self, Struct) -> StructDefinition);
126    filter_children_fn!(pub fn enums(&self, Enum) -> EnumDefinition);
127
128    as_inner_source!(pub fn as_contract(&self, Contract) -> ContractDefinition);
129    as_inner_source!(pub fn as_variable(&self, Variable) -> VariableDefinition);
130    as_inner_source!(pub fn as_function(&self, Function) -> FunctionDefinition);
131}
132
133/// A wrapper type around pt token.
134#[derive(Clone, Debug, PartialEq, Eq)]
135#[allow(clippy::large_enum_variant)]
136pub enum ParseSource {
137    /// Source contract definition.
138    Contract(Box<ContractDefinition>),
139    /// Source function definition.
140    Function(FunctionDefinition),
141    /// Source variable definition.
142    Variable(VariableDefinition),
143    /// Source event definition.
144    Event(EventDefinition),
145    /// Source error definition.
146    Error(ErrorDefinition),
147    /// Source struct definition.
148    Struct(StructDefinition),
149    /// Source enum definition.
150    Enum(EnumDefinition),
151    /// Source type definition.
152    Type(TypeDefinition),
153}
154
155impl ParseSource {
156    /// Get the identity of the source
157    pub fn ident(&self) -> String {
158        match self {
159            Self::Contract(contract) => contract.name.safe_unwrap().name.to_owned(),
160            Self::Variable(var) => var.name.safe_unwrap().name.to_owned(),
161            Self::Event(event) => event.name.safe_unwrap().name.to_owned(),
162            Self::Error(error) => error.name.safe_unwrap().name.to_owned(),
163            Self::Struct(structure) => structure.name.safe_unwrap().name.to_owned(),
164            Self::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(),
165            Self::Function(func) => {
166                func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned())
167            }
168            Self::Type(ty) => ty.name.name.to_owned(),
169        }
170    }
171
172    /// Get the signature of the source (for functions, includes parameter types)
173    pub fn signature(&self) -> String {
174        match self {
175            Self::Function(func) => function_signature(func),
176            _ => self.ident(),
177        }
178    }
179
180    /// Get the range of this item in the source file.
181    pub fn range(&self) -> Range<usize> {
182        match self {
183            Self::Contract(contract) => contract.loc,
184            Self::Variable(var) => var.loc,
185            Self::Event(event) => event.loc,
186            Self::Error(error) => error.loc,
187            Self::Struct(structure) => structure.loc,
188            Self::Enum(enumerable) => enumerable.loc,
189            Self::Function(func) => func.loc_prototype,
190            Self::Type(ty) => ty.loc,
191        }
192        .range()
193    }
194}