1use crate::{
2 CONTRACT_INHERITANCE_ID, CommentTag, Comments, CommentsRef, DEPLOYMENTS_ID, Document,
3 GIT_SOURCE_ID, INHERITDOC_ID, Markdown, PreprocessorOutput,
4 document::{DocumentContent, read_context},
5 helpers::function_signature,
6 parser::ParseSource,
7 solang_ext::SafeUnwrap,
8 writer::BufWriter,
9};
10use itertools::Itertools;
11use solang_parser::pt::{Base, FunctionDefinition, VariableAttribute};
12use std::path::Path;
13
14pub type AsDocResult = Result<String, std::fmt::Error>;
16
17pub trait AsDoc {
19 fn as_doc(&self) -> AsDocResult;
21}
22
23impl AsDoc for String {
24 fn as_doc(&self) -> AsDocResult {
25 Ok(self.to_owned())
26 }
27}
28
29impl AsDoc for Comments {
30 fn as_doc(&self) -> AsDocResult {
31 CommentsRef::from(self).as_doc()
32 }
33}
34
35impl AsDoc for CommentsRef<'_> {
36 fn as_doc(&self) -> AsDocResult {
38 let mut writer = BufWriter::default();
39
40 let titles = self.include_tag(CommentTag::Title);
42 if !titles.is_empty() {
43 writer.write_bold(&format!("Title{}:", if titles.len() == 1 { "" } else { "s" }))?;
44 writer.writeln_raw(titles.iter().map(|t| &t.value).join(", "))?;
45 writer.writeln()?;
46 }
47
48 let authors = self.include_tag(CommentTag::Author);
50 if !authors.is_empty() {
51 writer.write_bold(&format!("Author{}:", if authors.len() == 1 { "" } else { "s" }))?;
52 writer.writeln_raw(authors.iter().map(|a| &a.value).join(", "))?;
53 writer.writeln()?;
54 }
55
56 let notices = self.include_tag(CommentTag::Notice);
58 for n in notices.iter() {
59 writer.writeln_raw(&n.value)?;
60 writer.writeln()?;
61 }
62
63 let devs = self.include_tag(CommentTag::Dev);
65 for d in devs.iter() {
66 writer.write_dev_content(&d.value)?;
67 writer.writeln()?;
68 }
69
70 let customs = self.get_custom_tags();
72 if !customs.is_empty() {
73 writer.write_bold(&format!("Note{}:", if customs.len() == 1 { "" } else { "s" }))?;
74 for c in customs.iter() {
75 writer.writeln_raw(format!(
76 "{}{}: {}",
77 if customs.len() == 1 { "" } else { "- " },
78 &c.tag,
79 &c.value
80 ))?;
81 writer.writeln()?;
82 }
83 }
84
85 Ok(writer.finish())
86 }
87}
88
89impl AsDoc for Base {
90 fn as_doc(&self) -> AsDocResult {
91 Ok(self.name.identifiers.iter().map(|ident| ident.name.to_owned()).join("."))
92 }
93}
94
95impl AsDoc for Document {
96 fn as_doc(&self) -> AsDocResult {
97 let mut writer = BufWriter::default();
98
99 match &self.content {
100 DocumentContent::OverloadedFunctions(items) => {
101 writer
102 .write_title(&format!("function {}", items.first().unwrap().source.ident()))?;
103 if let Some(git_source) = read_context!(self, GIT_SOURCE_ID, GitSource) {
104 writer.write_link("Git Source", &git_source)?;
105 writer.writeln()?;
106 }
107
108 for item in items {
109 let func = item.as_function().unwrap();
110 let heading = function_signature(func).replace(',', ", ");
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 mut bases = vec![];
145 let linked =
146 read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance);
147 for base in &contract.base {
148 let base_doc = base.as_doc()?;
149 let base_ident = &base.name.identifiers.last().unwrap().name;
150
151 let link = linked
152 .as_ref()
153 .and_then(|link| {
154 link.get(base_ident).map(|path| {
155 let path = if cfg!(windows) {
156 Path::new("\\").join(path)
157 } else {
158 Path::new("/").join(path)
159 };
160 Markdown::Link(&base_doc, &path.display().to_string())
161 .as_doc()
162 })
163 })
164 .transpose()?
165 .unwrap_or(base_doc);
166
167 bases.push(link);
168 }
169
170 writer.writeln_raw(bases.join(", "))?;
171 writer.writeln()?;
172 }
173
174 writer.writeln_doc(&item.comments)?;
175
176 if let Some(all_vars) = item.variables() {
177 let (constants, state_vars): (Vec<_>, Vec<_>) =
178 all_vars.into_iter().partition(|(item, _, _)| {
179 item.attrs.iter().any(|attr| {
180 matches!(
181 attr,
182 VariableAttribute::Constant(_)
183 | VariableAttribute::Immutable(_)
184 )
185 })
186 });
187
188 if !constants.is_empty() {
189 writer.write_subtitle("Constants")?;
190 constants.into_iter().try_for_each(|(item, comments, code)| {
191 let comments = comments.merge_inheritdoc(
192 &item.name.safe_unwrap().name,
193 read_context!(self, INHERITDOC_ID, Inheritdoc),
194 );
195
196 writer.write_heading(&item.name.safe_unwrap().name)?;
197 writer.write_section(&comments, code)?;
198 writer.writeln()
199 })?;
200 }
201
202 if !state_vars.is_empty() {
203 writer.write_subtitle("State Variables")?;
204 state_vars.into_iter().try_for_each(|(item, comments, code)| {
205 let comments = comments.merge_inheritdoc(
206 &item.name.safe_unwrap().name,
207 read_context!(self, INHERITDOC_ID, Inheritdoc),
208 );
209
210 writer.write_heading(&item.name.safe_unwrap().name)?;
211 writer.write_section(&comments, code)?;
212 writer.writeln()
213 })?;
214 }
215 }
216
217 if let Some(funcs) = item.functions() {
218 writer.write_subtitle("Functions")?;
219
220 for (func, comments, code) in &funcs {
221 self.write_function(&mut writer, func, comments, code)?;
222 }
223 }
224
225 if let Some(events) = item.events() {
226 writer.write_subtitle("Events")?;
227 events.into_iter().try_for_each(|(item, comments, code)| {
228 writer.write_heading(&item.name.safe_unwrap().name)?;
229 writer.write_section(comments, code)?;
230 writer.try_write_events_table(&item.fields, comments)
231 })?;
232 }
233
234 if let Some(errors) = item.errors() {
235 writer.write_subtitle("Errors")?;
236 errors.into_iter().try_for_each(|(item, comments, code)| {
237 writer.write_heading(&item.name.safe_unwrap().name)?;
238 writer.write_section(comments, code)?;
239 writer.try_write_errors_table(&item.fields, comments)
240 })?;
241 }
242
243 if let Some(structs) = item.structs() {
244 writer.write_subtitle("Structs")?;
245 structs.into_iter().try_for_each(|(item, comments, code)| {
246 writer.write_heading(&item.name.safe_unwrap().name)?;
247 writer.write_section(comments, code)?;
248 writer.try_write_properties_table(&item.fields, comments)
249 })?;
250 }
251
252 if let Some(enums) = item.enums() {
253 writer.write_subtitle("Enums")?;
254 enums.into_iter().try_for_each(|(item, comments, code)| {
255 writer.write_heading(&item.name.safe_unwrap().name)?;
256 writer.write_section(comments, code)?;
257 writer.try_write_variant_table(item, comments)
258 })?;
259 }
260 }
261
262 ParseSource::Function(func) => {
263 writer.writeln_doc(
266 &item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]),
267 )?;
268
269 writer.write_code(&item.code)?;
271
272 let params =
274 func.params.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
275 writer.try_write_param_table(CommentTag::Param, ¶ms, &item.comments)?;
276
277 let returns =
279 func.returns.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
280 writer.try_write_param_table(
281 CommentTag::Return,
282 &returns,
283 &item.comments,
284 )?;
285
286 writer.writeln()?;
287 }
288
289 ParseSource::Struct(ty) => {
290 writer.write_section(&item.comments, &item.code)?;
291 writer.try_write_properties_table(&ty.fields, &item.comments)?;
292 }
293 ParseSource::Event(ev) => {
294 writer.write_section(&item.comments, &item.code)?;
295 writer.try_write_events_table(&ev.fields, &item.comments)?;
296 }
297 ParseSource::Error(err) => {
298 writer.write_section(&item.comments, &item.code)?;
299 writer.try_write_errors_table(&err.fields, &item.comments)?;
300 }
301 ParseSource::Variable(_) | ParseSource::Enum(_) | ParseSource::Type(_) => {
302 writer.write_section(&item.comments, &item.code)?;
303 }
304 }
305 }
306 DocumentContent::Empty => (),
307 };
308
309 Ok(writer.finish())
310 }
311}
312
313impl Document {
314 fn write_function(
316 &self,
317 writer: &mut BufWriter,
318 func: &FunctionDefinition,
319 comments: &Comments,
320 code: &str,
321 ) -> Result<(), std::fmt::Error> {
322 let func_sign = function_signature(func);
323 let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned());
324 let comments =
325 comments.merge_inheritdoc(&func_sign, read_context!(self, INHERITDOC_ID, Inheritdoc));
326
327 writer.write_heading(&func_name)?;
329
330 writer.writeln()?;
331
332 writer.writeln_doc(&comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?;
334
335 writer.write_code(code)?;
337
338 let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
340 writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?;
341
342 let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::<Vec<_>>();
344 writer.try_write_param_table(CommentTag::Return, &returns, &comments)?;
345
346 writer.writeln()?;
347 Ok(())
348 }
349}