1use crate::{
2 document::{read_context, DocumentContent},
3 parser::ParseSource,
4 writer::BufWriter,
5 CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput,
6 CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID,
7};
8use forge_fmt::solang_ext::SafeUnwrap;
9use itertools::Itertools;
10use solang_parser::pt::{Base, FunctionDefinition};
11use std::path::{Path, PathBuf};
12
13pub type AsDocResult = Result<String, std::fmt::Error>;
15
16pub trait AsDoc {
18 fn as_doc(&self) -> AsDocResult;
20}
21
22impl AsDoc for String {
23 fn as_doc(&self) -> AsDocResult {
24 Ok(self.to_owned())
25 }
26}
27
28impl AsDoc for Comments {
29 fn as_doc(&self) -> AsDocResult {
30 CommentsRef::from(self).as_doc()
31 }
32}
33
34impl AsDoc for CommentsRef<'_> {
35 fn as_doc(&self) -> AsDocResult {
37 let mut writer = BufWriter::default();
38
39 let authors = self.include_tag(CommentTag::Author);
41 if !authors.is_empty() {
42 writer.write_bold(&format!("Author{}:", if authors.len() == 1 { "" } else { "s" }))?;
43 writer.writeln_raw(authors.iter().map(|a| &a.value).join(", "))?;
44 writer.writeln()?;
45 }
46
47 let notices = self.include_tag(CommentTag::Notice);
49 for n in notices.iter() {
50 writer.writeln_raw(&n.value)?;
51 writer.writeln()?;
52 }
53
54 let devs = self.include_tag(CommentTag::Dev);
56 for d in devs.iter() {
57 writer.write_italic(&d.value)?;
58 writer.writeln()?;
59 }
60
61 let customs = self.get_custom_tags();
63 if !customs.is_empty() {
64 writer.write_bold(&format!("Note{}:", if customs.len() == 1 { "" } else { "s" }))?;
65 for c in customs.iter() {
66 writer.writeln_raw(format!(
67 "{}{}: {}",
68 if customs.len() == 1 { "" } else { "- " },
69 &c.tag,
70 &c.value
71 ))?;
72 writer.writeln()?;
73 }
74 }
75
76 Ok(writer.finish())
77 }
78}
79
80impl AsDoc for Base {
81 fn as_doc(&self) -> AsDocResult {
82 Ok(self.name.identifiers.iter().map(|ident| ident.name.to_owned()).join("."))
83 }
84}
85
86impl AsDoc for Document {
87 fn as_doc(&self) -> AsDocResult {
88 let mut writer = BufWriter::default();
89
90 match &self.content {
91 DocumentContent::OverloadedFunctions(items) => {
92 writer
93 .write_title(&format!("function {}", items.first().unwrap().source.ident()))?;
94 if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
95 writer.write_link("Git Source", &git_source)?;
96 writer.writeln()?;
97 }
98
99 for item in items {
100 let func = item.as_function().unwrap();
101 let mut heading = item.source.ident();
102 if !func.params.is_empty() {
103 heading.push_str(&format!(
104 "({})",
105 func.params
106 .iter()
107 .map(|p| p.1.as_ref().map(|p| p.ty.to_string()).unwrap_or_default())
108 .join(", ")
109 ));
110 }
111 writer.write_heading(&heading)?;
112 writer.write_section(&item.comments, &item.code)?;
113 }
114 }
115 DocumentContent::Constants(items) => {
116 writer.write_title("Constants")?;
117 if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
118 writer.write_link("Git Source", &git_source)?;
119 writer.writeln()?;
120 }
121
122 for item in items {
123 let var = item.as_variable().unwrap();
124 writer.write_heading(&var.name.safe_unwrap().name)?;
125 writer.write_section(&item.comments, &item.code)?;
126 }
127 }
128 DocumentContent::Single(item) => {
129 writer.write_title(&item.source.ident())?;
130 if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
131 writer.write_link("Git Source", &git_source)?;
132 writer.writeln()?;
133 }
134
135 if let Some(deployments) = read_context!(self, DEPLOYMENTS_ID, Deployments) {
136 writer.write_deployments_table(deployments)?;
137 }
138
139 match &item.source {
140 ParseSource::Contract(contract) => {
141 if !contract.base.is_empty() {
142 writer.write_bold("Inherits:")?;
143
144 let src_target_dir = self.target_src_dir();
146
147 let mut bases = vec![];
148 let linked =
149 read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance);
150 for base in &contract.base {
151 let base_doc = base.as_doc()?;
152 let base_ident = &base.name.identifiers.last().unwrap().name;
153
154 let link = linked
155 .as_ref()
156 .and_then(|link| {
157 link.get(base_ident).map(|path| {
158 let path = Path::new("/").join(
159 path.strip_prefix(&src_target_dir)
160 .ok()
161 .unwrap_or(path),
162 );
163 Markdown::Link(&base_doc, &path.display().to_string())
164 .as_doc()
165 })
166 })
167 .transpose()?
168 .unwrap_or(base_doc);
169
170 bases.push(link);
171 }
172
173 writer.writeln_raw(bases.join(", "))?;
174 writer.writeln()?;
175 }
176
177 writer.writeln_doc(&item.comments)?;
178
179 if let Some(state_vars) = item.variables() {
180 writer.write_subtitle("State Variables")?;
181 state_vars.into_iter().try_for_each(|(item, comments, code)| {
182 let comments = comments.merge_inheritdoc(
183 &item.name.safe_unwrap().name,
184 read_context!(self, INHERITDOC_ID, Inheritdoc),
185 );
186
187 writer.write_heading(&item.name.safe_unwrap().name)?;
188 writer.write_section(&comments, code)?;
189 writer.writeln()
190 })?;
191 }
192
193 if let Some(funcs) = item.functions() {
194 writer.write_subtitle("Functions")?;
195
196 for (func, comments, code) in &funcs {
197 self.write_function(&mut writer, func, comments, code)?;
198 }
199 }
200
201 if let Some(events) = item.events() {
202 writer.write_subtitle("Events")?;
203 events.into_iter().try_for_each(|(item, comments, code)| {
204 writer.write_heading(&item.name.safe_unwrap().name)?;
205 writer.write_section(comments, code)?;
206 writer.try_write_events_table(&item.fields, comments)
207 })?;
208 }
209
210 if let Some(errors) = item.errors() {
211 writer.write_subtitle("Errors")?;
212 errors.into_iter().try_for_each(|(item, comments, code)| {
213 writer.write_heading(&item.name.safe_unwrap().name)?;
214 writer.write_section(comments, code)?;
215 writer.try_write_errors_table(&item.fields, comments)
216 })?;
217 }
218
219 if let Some(structs) = item.structs() {
220 writer.write_subtitle("Structs")?;
221 structs.into_iter().try_for_each(|(item, comments, code)| {
222 writer.write_heading(&item.name.safe_unwrap().name)?;
223 writer.write_section(comments, code)?;
224 writer.try_write_properties_table(&item.fields, comments)
225 })?;
226 }
227
228 if let Some(enums) = item.enums() {
229 writer.write_subtitle("Enums")?;
230 enums.into_iter().try_for_each(|(item, comments, code)| {
231 writer.write_heading(&item.name.safe_unwrap().name)?;
232 writer.write_section(comments, code)
233 })?;
234 }
235 }
236
237 ParseSource::Function(func) => {
238 writer.writeln_doc(
241 &item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]),
242 )?;
243
244 writer.write_code(&item.code)?;
246
247 let params =
249 func.params.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
250 writer.try_write_param_table(CommentTag::Param, ¶ms, &item.comments)?;
251
252 let returns =
254 func.returns.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
255 writer.try_write_param_table(
256 CommentTag::Return,
257 &returns,
258 &item.comments,
259 )?;
260
261 writer.writeln()?;
262 }
263
264 ParseSource::Struct(ty) => {
265 writer.write_section(&item.comments, &item.code)?;
266 writer.try_write_properties_table(&ty.fields, &item.comments)?;
267 }
268 ParseSource::Event(ev) => {
269 writer.write_section(&item.comments, &item.code)?;
270 writer.try_write_events_table(&ev.fields, &item.comments)?;
271 }
272 ParseSource::Error(err) => {
273 writer.write_section(&item.comments, &item.code)?;
274 writer.try_write_errors_table(&err.fields, &item.comments)?;
275 }
276 ParseSource::Variable(_) | ParseSource::Enum(_) | ParseSource::Type(_) => {
277 writer.write_section(&item.comments, &item.code)?;
278 }
279 }
280 }
281 DocumentContent::Empty => (),
282 };
283
284 Ok(writer.finish())
285 }
286}
287
288impl Document {
289 fn target_src_dir(&self) -> PathBuf {
291 self.out_target_dir.join("src")
292 }
293
294 fn write_function(
296 &self,
297 writer: &mut BufWriter,
298 func: &FunctionDefinition,
299 comments: &Comments,
300 code: &str,
301 ) -> Result<(), std::fmt::Error> {
302 let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned());
303 let comments =
304 comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc));
305
306 writer.write_heading(&func_name)?;
308
309 writer.writeln()?;
310
311 writer.writeln_doc(&comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?;
313
314 writer.write_code(code)?;
316
317 let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
319 writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?;
320
321 let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
323 writer.try_write_param_table(CommentTag::Return, &returns, &comments)?;
324
325 writer.writeln()?;
326 Ok(())
327 }
328}