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