1use crate::{
3 doc::module::ModuleInfo,
4 render::{
5 item::type_anchor::render_type_anchor,
6 link::{DocLink, DocLinks},
7 title::BlockTitle,
8 title::DocBlock,
9 util::format::docstring::DocStrings,
10 DocStyle, Renderable, IDENTITY,
11 },
12 RenderPlan,
13};
14use anyhow::Result;
15use horrorshow::{box_html, Raw, RenderBox, Template};
16use std::{collections::BTreeMap, fmt::Write};
17use sway_core::language::ty::{
18 TyConstantDecl, TyEnumVariant, TyFunctionDecl, TyImplSelfOrTrait, TyStorageField,
19 TyStructField, TyTraitFn, TyTraitItem, TyTraitType,
20};
21use sway_types::Spanned;
22
23#[derive(Clone, Debug)]
41pub struct Context {
42 module_info: ModuleInfo,
43 context_type: ContextType,
44}
45
46impl Context {
47 pub fn new(module_info: ModuleInfo, context_type: ContextType) -> Self {
48 Self {
49 module_info,
50 context_type,
51 }
52 }
53}
54
55impl Renderable for Context {
56 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
57 let mut rendered_list: Vec<String> = Vec::new();
58 let mut is_method_block = false;
59 match &self.context_type {
60 ContextType::StructFields(fields) => {
61 for field in fields {
62 let struct_field_id = format!("structfield.{}", field.name.as_str());
63 let type_anchor = render_type_anchor(
64 (*render_plan.engines.te().get(field.type_argument.type_id)).clone(),
65 &render_plan,
66 &self.module_info,
67 );
68 rendered_list.push(box_html! {
69 span(id=&struct_field_id, class="structfield small-section-header") {
70 a(class="anchor field", href=format!("{IDENTITY}{struct_field_id}"));
71 code {
72 : format!("{}: ", field.name.as_str());
73 @ if let Ok(type_anchor) = type_anchor {
74 : type_anchor;
75 } else {
76 : field.type_argument.span().as_str();
77 }
78 }
79 }
80 @ if !field.attributes.is_empty() {
81 div(class="docblock") {
82 : Raw(field.attributes.to_html_string());
83 }
84 }
85 }.into_string()?);
86 }
87 }
88 ContextType::StorageFields(fields) => {
89 for field in fields {
90 let storage_field_id = format!("storagefield.{}", field.name.as_str());
91 let type_anchor = render_type_anchor(
92 (*render_plan.engines.te().get(field.type_argument.type_id)).clone(),
93 &render_plan,
94 &self.module_info,
95 );
96 rendered_list.push(box_html! {
97 span(id=&storage_field_id, class="storagefield small-section-header") {
98 a(class="anchor field", href=format!("{IDENTITY}{storage_field_id}"));
99 code {
100 : format!("{}: ", field.name.as_str());
101 @ if let Ok(type_anchor) = type_anchor {
102 : type_anchor;
103 } else {
104 : field.type_argument.span().as_str();
105 }
106 }
107 }
108 @ if !field.attributes.is_empty() {
109 div(class="docblock") {
110 : Raw(field.attributes.to_html_string());
111 }
112 }
113 }.into_string()?);
114 }
115 }
116 ContextType::EnumVariants(variants) => {
117 for variant in variants {
118 let enum_variant_id = format!("variant.{}", variant.name.as_str());
119 let type_anchor = render_type_anchor(
120 (*render_plan.engines.te().get(variant.type_argument.type_id)).clone(),
121 &render_plan,
122 &self.module_info,
123 );
124 rendered_list.push(box_html! {
125 h3(id=&enum_variant_id, class="variant small-section-header") {
126 a(class="anchor field", href=format!("{IDENTITY}{enum_variant_id}"));
127 code {
128 : format!("{}: ", variant.name.as_str());
129 @ if let Ok(type_anchor) = type_anchor {
130 : type_anchor;
131 } else {
132 : variant.type_argument.span().as_str();
133 }
134 }
135 }
136 @ if !variant.attributes.is_empty() {
137 div(class="docblock") {
138 : Raw(variant.attributes.to_html_string());
139 }
140 }
141 }.into_string()?);
142 }
143 }
144 ContextType::RequiredMethods(methods) => {
145 is_method_block = true;
146 for method in methods {
147 let mut fn_sig = format!("fn {}(", method.name.as_str());
148 for param in &method.parameters {
149 let mut param_str = String::new();
150 if param.is_reference {
151 write!(param_str, "ref ")?;
152 }
153 if param.is_mutable {
154 write!(param_str, "mut ")?;
155 }
156 if param.is_self() {
157 write!(param_str, "self,")?;
158 } else {
159 write!(
160 fn_sig,
161 "{} {},",
162 param.name.as_str(),
163 param.type_argument.span().as_str()
164 )?;
165 }
166 }
167 write!(fn_sig, ") -> {}", method.return_type.span().as_str())?;
168 let multiline = fn_sig.chars().count() >= 60;
169 let fn_sig = format!("fn {}(", method.name);
170 let method_id = format!("tymethod.{}", method.name.as_str());
171 let method_attrs = method.attributes.clone();
172
173 let rendered_method = box_html! {
174 div(id=&method_id, class="method has-srclink") {
175 a(href=format!("{IDENTITY}{method_id}"), class="anchor");
176 h4(class="code-header") {
177 : "fn ";
178 a(class="fnname", href=format!("{IDENTITY}{method_id}")) {
179 : method.name.as_str();
180 }
181 : "(";
182 @ if multiline {
183 @ for param in &method.parameters {
184 br;
185 : " ";
186 @ if param.is_reference {
187 : "ref";
188 }
189 @ if param.is_mutable {
190 : "mut ";
191 }
192 @ if param.is_self() {
193 : "self,"
194 } else {
195 : param.name.as_str();
196 : ": ";
197 : param.type_argument.span().as_str();
198 : ","
199 }
200 }
201 br;
202 : ")";
203 } else {
204 @ for param in &method.parameters {
205 @ if param.is_reference {
206 : "ref";
207 }
208 @ if param.is_mutable {
209 : "mut ";
210 }
211 @ if param.is_self() {
212 : "self"
213 } else {
214 : param.name.as_str();
215 : ": ";
216 : param.type_argument.span().as_str();
217 }
218 @ if param.name.as_str()
219 != method.parameters.last()
220 .expect("no last element in trait method parameters list")
221 .name.as_str() {
222 : ", ";
223 }
224 }
225 : ")";
226 }
227 @ if !method.return_type.span().as_str().contains(&fn_sig) {
228 : " -> ";
229 : method.return_type.span().as_str();
230 }
231 }
232 }
233 }.into_string()?;
234
235 rendered_list.push(
236 box_html! {
237 @ if !method_attrs.is_empty() {
238 details(class="swaydoc-toggle open") {
239 summary {
240 : Raw(rendered_method);
241 }
242 div(class="docblock") {
243 : Raw(method_attrs.to_html_string());
244 }
245 }
246 } else {
247 : Raw(rendered_method);
248 }
249 }
250 .into_string()?,
251 );
252 }
253 }
254 };
255 Ok(box_html! {
256 @ if is_method_block {
257 div(class="methods") {
258 @ for item in rendered_list {
259 : Raw(item);
260 }
261 }
262 } else {
263 @ for item in rendered_list {
264 : Raw(item);
265 }
266 }
267 })
268 }
269}
270
271#[derive(Debug, Clone)]
272pub struct DocImplTrait {
273 pub impl_for_module: ModuleInfo,
274 pub impl_trait: TyImplSelfOrTrait,
275 pub module_info_override: Option<Vec<String>>,
276}
277
278impl DocImplTrait {
279 pub fn short_name(&self) -> String {
280 self.impl_trait.trait_name.suffix.as_str().to_string()
281 }
282
283 pub fn type_args(&self) -> Vec<String> {
284 self.impl_trait
285 .trait_type_arguments
286 .iter()
287 .map(|arg| arg.span().as_str().to_string())
288 .collect()
289 }
290
291 pub fn name_with_type_args(&self) -> String {
292 let type_args = self.type_args();
293 if !type_args.is_empty() {
294 format!("{}<{}>", self.short_name(), type_args.join(", "))
295 } else {
296 self.short_name()
297 }
298 }
299
300 pub fn is_inherent(&self) -> bool {
303 self.short_name() == self.impl_trait.implementing_for.span().as_str()
304 || self.short_name() == "r#Self"
305 }
306}
307
308#[derive(Clone, Debug, Default)]
309pub struct ItemContext {
311 pub context_opt: Option<Context>,
313 pub inherent_impls: Option<Vec<DocImplTrait>>,
315 pub impl_traits: Option<Vec<DocImplTrait>>,
317}
318
319impl ItemContext {
320 pub fn to_doclinks(&self) -> DocLinks {
321 let mut links: BTreeMap<BlockTitle, Vec<DocLink>> = BTreeMap::new();
322 if let Some(context) = &self.context_opt {
323 match context.context_type.clone() {
324 ContextType::StructFields(fields) => {
325 let doc_links = fields
326 .iter()
327 .map(|field| DocLink {
328 name: field.name.as_str().to_string(),
329 module_info: ModuleInfo::from_ty_module(vec![], None),
330 html_filename: format!(
331 "{}structfield.{}",
332 IDENTITY,
333 field.name.as_str()
334 ),
335 preview_opt: None,
336 })
337 .collect();
338 links.insert(BlockTitle::Fields, doc_links);
339 }
340 ContextType::StorageFields(fields) => {
341 let doc_links = fields
342 .iter()
343 .map(|field| DocLink {
344 name: field.name.as_str().to_string(),
345 module_info: ModuleInfo::from_ty_module(vec![], None),
346 html_filename: format!(
347 "{}storagefield.{}",
348 IDENTITY,
349 field.name.as_str()
350 ),
351 preview_opt: None,
352 })
353 .collect();
354 links.insert(BlockTitle::Fields, doc_links);
355 }
356 ContextType::EnumVariants(variants) => {
357 let doc_links = variants
358 .iter()
359 .map(|variant| DocLink {
360 name: variant.name.as_str().to_string(),
361 module_info: ModuleInfo::from_ty_module(vec![], None),
362 html_filename: format!("{}variant.{}", IDENTITY, variant.name.as_str()),
363 preview_opt: None,
364 })
365 .collect();
366 links.insert(BlockTitle::Variants, doc_links);
367 }
368 ContextType::RequiredMethods(methods) => {
369 let doc_links = methods
370 .iter()
371 .map(|method| DocLink {
372 name: method.name.as_str().to_string(),
373 module_info: ModuleInfo::from_ty_module(vec![], None),
374 html_filename: format!(
375 "{}structfield.{}",
376 IDENTITY,
377 method.name.as_str()
378 ),
379 preview_opt: None,
380 })
381 .collect();
382 links.insert(BlockTitle::RequiredMethods, doc_links);
383 }
384 }
385 }
386
387 if let Some(inherent_impls) = &self.inherent_impls {
388 let mut doc_links = Vec::new();
389 for inherent_impl in inherent_impls {
390 for item in &inherent_impl.impl_trait.items {
391 if let TyTraitItem::Fn(item_fn) = item {
392 let method_name = item_fn.name().to_string();
393 doc_links.push(DocLink {
394 name: method_name.clone(),
395 module_info: inherent_impl.impl_for_module.clone(),
396 html_filename: format!("{IDENTITY}method.{method_name}"),
397 preview_opt: None,
398 })
399 }
400 }
401 }
402 links.insert(BlockTitle::ImplMethods, doc_links);
403 }
404
405 if let Some(impl_traits) = &self.impl_traits {
406 let doc_links = impl_traits
407 .iter()
408 .map(|impl_trait| DocLink {
409 name: impl_trait.name_with_type_args(),
410 module_info: impl_trait.impl_for_module.clone(),
411 html_filename: format!("{}impl-{}", IDENTITY, impl_trait.name_with_type_args()),
412 preview_opt: None,
413 })
414 .collect();
415 links.insert(BlockTitle::ImplTraits, doc_links);
416 }
417
418 DocLinks {
419 style: DocStyle::Item {
420 title: None,
421 name: None,
422 },
423 links,
424 }
425 }
426}
427impl Renderable for ItemContext {
428 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
429 let context_opt = match self.context_opt {
430 Some(context) => {
431 let title = context.context_type.title();
432 let rendered_list = context.render(render_plan.clone())?;
433 let lct = title.html_title_string();
434 Some(
435 box_html! {
436 h2(id=&lct, class=format!("{} small-section-header", &lct)) {
437 : title.as_str();
438 a(class="anchor", href=format!("{IDENTITY}{lct}"));
439 }
440 : rendered_list;
441 }
442 .into_string()?,
443 )
444 }
445 None => None,
446 };
447
448 let impl_traits = match self.impl_traits {
449 Some(impl_traits) => {
450 let mut impl_trait_vec: Vec<_> = Vec::with_capacity(impl_traits.len());
451 for impl_trait in impl_traits {
452 impl_trait_vec.push(impl_trait.render(render_plan.clone())?);
453 }
454 impl_trait_vec
455 }
456 None => vec![],
457 };
458
459 let inherent_impls = match self.inherent_impls {
460 Some(inherent_impls) => {
461 let mut inherent_impl_vec: Vec<_> = Vec::with_capacity(inherent_impls.len());
462 for inherent_impl in inherent_impls {
463 inherent_impl_vec.push(inherent_impl.render(render_plan.clone())?);
464 }
465 inherent_impl_vec
466 }
467 None => vec![],
468 };
469
470 Ok(box_html! {
471 @ if let Some(context) = context_opt {
472 : Raw(context);
473 }
474 @ if !inherent_impls.is_empty() {
475 h2(id="methods", class="small-section-header") {
476 : "Implementations";
477 a(href=format!("{IDENTITY}methods"), class="anchor");
478 }
479 div(id="methods-list") {
480 @ for inherent_impl in inherent_impls {
481 : inherent_impl;
482 }
483 }
484 }
485 @ if !impl_traits.is_empty() {
486 h2(id="trait-implementations", class="small-section-header") {
487 : "Trait Implementations";
488 a(href=format!("{IDENTITY}trait-implementations"), class="anchor");
489 }
490 div(id="trait-implementations-list") {
491 @ for impl_trait in impl_traits {
492 : impl_trait;
493 }
494 }
495 }
496 })
497 }
498}
499impl Renderable for DocImplTrait {
500 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
501 let TyImplSelfOrTrait {
502 trait_name,
503 items,
504 implementing_for,
505 ..
506 } = &self.impl_trait;
507 let short_name = self.short_name();
508 let name_with_type_args = self.name_with_type_args();
509 let type_args = self.type_args();
510 let is_inherent = self.is_inherent();
511 let impl_for_module = &self.impl_for_module;
512 let no_deps = render_plan.no_deps;
513 let is_external_item = if let Some(project_root) = trait_name.prefixes.first() {
514 project_root.as_str() != impl_for_module.project_name()
515 } else {
516 false
517 };
518
519 let trait_link = if let Some(module_prefixes) = &self.module_info_override {
520 ModuleInfo::from_vec_str(module_prefixes).file_path_from_location(
521 &format!("trait.{short_name}.html"),
522 impl_for_module,
523 is_external_item,
524 )?
525 } else {
526 ModuleInfo::from_call_path(trait_name).file_path_from_location(
527 &format!("trait.{short_name}.html"),
528 impl_for_module,
529 is_external_item,
530 )?
531 };
532
533 let mut rendered_items = Vec::with_capacity(items.len());
534 for item in items {
535 rendered_items.push(item.clone().render(render_plan.clone())?)
536 }
537
538 let impl_for = box_html! {
539 div(id=format!("impl-{}", name_with_type_args), class="impl has-srclink") {
540 a(href=format!("{IDENTITY}impl-{}", name_with_type_args), class="anchor");
541 h3(class="code-header in-band") {
542 : "impl ";
543 @ if !is_inherent {
544 @ if no_deps && is_external_item {
545 : name_with_type_args;
546 } else {
547 a(class="trait", href=format!("{trait_link}")) {
548 : short_name;
549 }
550 @ for arg in &type_args {
551 @ if arg == type_args.first().unwrap() {
552 : "<";
553 }
554 : arg;
555 @ if arg != type_args.last().unwrap() {
556 : ", ";
557 }
558 @ if arg == type_args.last().unwrap() {
559 : ">";
560 }
561 }
562 }
563 : " for ";
564 }
565 : implementing_for.span().as_str();
566 }
567 }
568 }
569 .into_string()?;
570
571 Ok(box_html! {
572 @ if !rendered_items.is_empty() {
574 details(class="swaydoc-toggle implementors-toggle", open) {
575 summary {
576 : Raw(impl_for);
577 }
578 div(class="impl-items") {
579 @ for item in rendered_items {
580 : item;
581 }
582 }
583 }
584 } else {
585 : Raw(impl_for);
586 }
587 })
588 }
589}
590impl Renderable for TyTraitItem {
591 fn render(self, render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
592 match self {
593 TyTraitItem::Fn(decl_ref) => {
594 let decl = render_plan.engines.de().get_function(decl_ref.id());
595 <TyFunctionDecl as Clone>::clone(&decl).render(render_plan)
596 }
597 TyTraitItem::Constant(ref decl_ref) => {
598 let decl = render_plan.engines.de().get_constant(decl_ref.id());
599 <TyConstantDecl as Clone>::clone(&decl).render(render_plan)
600 }
601 TyTraitItem::Type(ref decl_ref) => {
602 let decl = render_plan.engines.de().get_type(decl_ref.id());
603 <TyTraitType as Clone>::clone(&decl).render(render_plan)
604 }
605 }
606 }
607}
608
609impl Renderable for TyFunctionDecl {
610 fn render(self, _render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
611 let attributes = self.attributes.to_html_string();
612
613 let mut fn_sig = format!("fn {}(", self.name.as_str());
614 for param in self.parameters.iter() {
615 let mut param_str = String::new();
616 if param.is_reference {
617 write!(param_str, "ref ")?;
618 }
619 if param.is_mutable {
620 write!(param_str, "mut ")?;
621 }
622 if param.is_self() {
623 write!(param_str, "self,")?;
624 } else {
625 write!(
626 fn_sig,
627 "{} {},",
628 param.name.as_str(),
629 param.type_argument.span().as_str()
630 )?;
631 }
632 }
633 write!(fn_sig, ") -> {}", self.return_type.span().as_str())?;
634 let multiline = fn_sig.chars().count() >= 60;
635
636 let method_id = format!("method.{}", self.name.as_str());
637
638 let impl_list = box_html! {
639 div(id=format!("{method_id}"), class="method trait-impl") {
640 a(href=format!("{IDENTITY}{method_id}"), class="anchor");
641 h4(class="code-header") {
642 @ if self.visibility.is_public() {
643 : "pub ";
644 }
645 : "fn ";
646 a(class="fnname", href=format!("{IDENTITY}{method_id}")) {
647 : self.name.as_str();
648 }
649 : "(";
650 @ if multiline {
651 @ for param in self.parameters.iter() {
652 br;
653 : " ";
654 @ if param.is_reference {
655 : "ref";
656 }
657 @ if param.is_mutable {
658 : "mut ";
659 }
660 @ if param.is_self() {
661 : "self,"
662 } else {
663 : param.name.as_str();
664 : ": ";
665 : param.type_argument.span().as_str();
666 : ","
667 }
668 }
669 br;
670 : ")";
671 } else {
672 @ for param in self.parameters.iter() {
673 @ if param.is_reference {
674 : "ref";
675 }
676 @ if param.is_mutable {
677 : "mut ";
678 }
679 @ if param.is_self() {
680 : "self"
681 } else {
682 : param.name.as_str();
683 : ": ";
684 : param.type_argument.span().as_str();
685 }
686 @ if param.name.as_str()
687 != self.parameters.last()
688 .expect("no last element in trait method parameters list")
689 .name.as_str() {
690 : ", ";
691 }
692 }
693 : ")";
694 }
695 @ if self.span.as_str().contains("->") {
696 : " -> ";
697 : self.return_type.span().as_str();
698 }
699 }
700 }
701 }
702 .into_string()?;
703
704 Ok(box_html! {
705 @ if !attributes.is_empty() {
706 details(class="swaydoc-toggle method-toggle", open) {
707 summary {
708 : Raw(impl_list);
709 }
710 div(class="docblock") {
711 : Raw(attributes);
712 }
713 }
714 } else {
715 : Raw(impl_list);
716 }
717 })
718 }
719}
720
721impl Renderable for TyTraitType {
722 fn render(self, _render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
723 let attributes = self.attributes.to_html_string();
724 let trait_type_id = format!("traittype.{}", self.name.as_str());
725 let contents = box_html! {
726 div(id=format!("{trait_type_id}"), class="type trait-impl") {
727 a(href=format!("{IDENTITY}{trait_type_id}"), class="anchor");
728 h4(class="code-header") {
729 : self.span.as_str();
730 }
731 }
732 }
733 .into_string()?;
734
735 Ok(box_html! {
736 @ if !attributes.is_empty() {
737 details(class="swaydoc-toggle method-toggle", open) {
738 summary {
739 : Raw(contents);
740 }
741 div(class="docblock") {
742 : Raw(attributes);
743 }
744 }
745 } else {
746 : Raw(contents);
747 }
748 })
749 }
750}
751
752impl Renderable for TyConstantDecl {
753 fn render(self, _render_plan: RenderPlan) -> Result<Box<dyn RenderBox>> {
754 let attributes = self.attributes.to_html_string();
755 let const_id = format!("const.{}", self.call_path.suffix.as_str());
756 let contents = box_html! {
757 div(id=format!("{const_id}"), class="const trait-impl") {
758 a(href=format!("{IDENTITY}{const_id}"), class="anchor");
759 h4(class="code-header") {
760 : self.span.as_str();
761 }
762 }
763 }
764 .into_string()?;
765
766 Ok(box_html! {
767 @ if !attributes.is_empty() {
768 details(class="swaydoc-toggle method-toggle", open) {
769 summary {
770 : Raw(contents);
771 }
772 div(class="docblock") {
773 : Raw(attributes);
774 }
775 }
776 } else {
777 : Raw(contents);
778 }
779 })
780 }
781}
782
783#[derive(Clone, Debug)]
784pub enum ContextType {
787 StructFields(Vec<TyStructField>),
789 StorageFields(Vec<TyStorageField>),
791 EnumVariants(Vec<TyEnumVariant>),
793 RequiredMethods(Vec<TyTraitFn>),
795}
796impl DocBlock for ContextType {
797 fn title(&self) -> BlockTitle {
798 match self {
799 ContextType::StructFields(_) | ContextType::StorageFields(_) => BlockTitle::Fields,
800 ContextType::EnumVariants(_) => BlockTitle::Variants,
801 ContextType::RequiredMethods(_) => BlockTitle::RequiredMethods,
802 }
803 }
804
805 fn name(&self) -> &str {
806 match self {
807 ContextType::StructFields(_) => "struct_fields",
808 ContextType::StorageFields(_) => "storage_fields",
809 ContextType::EnumVariants(_) => "enum_variants",
810 ContextType::RequiredMethods(_) => "required_methods",
811 }
812 }
813}