1use js_sys::Array;
11use serde::{Deserialize, Serialize};
12use std::format;
13use std::string::{String, ToString};
14use std::vec::Vec;
15use wasm_bindgen::prelude::*;
16
17#[wasm_bindgen]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum DocFormat {
21 HTML,
23 Markdown,
25 JSON,
27 OpenAPI,
29 TypeDoc,
31}
32
33#[wasm_bindgen]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum DocTheme {
37 Default,
39 Dark,
41 Material,
43 Bootstrap,
45 Minimal,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub enum TSNodeType {
52 Interface,
53 Class,
54 Function,
55 Method,
56 Property,
57 Parameter,
58 Enum,
59 Type,
60 Module,
61 Namespace,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TSNode {
67 pub name: String,
68 pub node_type: TSNodeType,
69 pub description: Option<String>,
70 pub parameters: Vec<TSParameter>,
71 pub return_type: Option<String>,
72 pub properties: Vec<TSProperty>,
73 pub examples: Vec<String>,
74 pub deprecated: bool,
75 pub since_version: Option<String>,
76 pub file_path: String,
77 pub line_number: u32,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct TSParameter {
83 pub name: String,
84 pub param_type: String,
85 pub optional: bool,
86 pub default_value: Option<String>,
87 pub description: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct TSProperty {
93 pub name: String,
94 pub property_type: String,
95 pub optional: bool,
96 pub readonly: bool,
97 pub description: Option<String>,
98}
99
100#[wasm_bindgen]
102#[derive(Debug, Clone)]
103pub struct DocConfig {
104 format: DocFormat,
105 theme: DocTheme,
106 include_examples: bool,
107 include_source_links: bool,
108 include_private: bool,
109 generate_playground: bool,
110 output_directory: String,
111 base_url: String,
112 title: String,
113 version: String,
114}
115
116impl Default for DocConfig {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122#[wasm_bindgen]
123impl DocConfig {
124 #[wasm_bindgen(constructor)]
126 pub fn new() -> Self {
127 Self {
128 format: DocFormat::HTML,
129 theme: DocTheme::Default,
130 include_examples: true,
131 include_source_links: true,
132 include_private: false,
133 generate_playground: true,
134 output_directory: "./docs".to_string(),
135 base_url: "/".to_string(),
136 title: "TrustFormer WASM API".to_string(),
137 version: "1.0.0".to_string(),
138 }
139 }
140
141 pub fn set_format(&mut self, format: DocFormat) {
143 self.format = format;
144 }
145
146 pub fn set_theme(&mut self, theme: DocTheme) {
148 self.theme = theme;
149 }
150
151 pub fn set_include_examples(&mut self, include: bool) {
153 self.include_examples = include;
154 }
155
156 pub fn set_include_source_links(&mut self, include: bool) {
158 self.include_source_links = include;
159 }
160
161 pub fn set_include_private(&mut self, include: bool) {
163 self.include_private = include;
164 }
165
166 pub fn set_generate_playground(&mut self, generate: bool) {
168 self.generate_playground = generate;
169 }
170
171 pub fn set_output_directory(&mut self, dir: String) {
173 self.output_directory = dir;
174 }
175
176 pub fn set_base_url(&mut self, url: String) {
178 self.base_url = url;
179 }
180
181 pub fn set_title(&mut self, title: String) {
183 self.title = title;
184 }
185
186 pub fn set_version(&mut self, version: String) {
188 self.version = version;
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct GeneratedDoc {
195 pub title: String,
196 pub version: String,
197 pub generated_at: f64,
198 pub format: DocFormat,
199 pub content: String,
200 pub assets: Vec<DocAsset>,
201 pub navigation: DocNavigation,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct DocAsset {
207 pub filename: String,
208 pub content: String,
209 pub mime_type: String,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct DocNavigation {
215 pub sections: Vec<NavSection>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct NavSection {
221 pub title: String,
222 pub items: Vec<NavItem>,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct NavItem {
228 pub title: String,
229 pub link: String,
230 pub children: Vec<NavItem>,
231}
232
233#[wasm_bindgen]
235pub struct AutoDocGenerator {
236 config: DocConfig,
237 ts_nodes: Vec<TSNode>,
238 examples: Vec<CodeExample>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct CodeExample {
244 pub title: String,
245 pub description: String,
246 pub code: String,
247 pub language: String,
248 pub category: String,
249}
250
251#[wasm_bindgen]
252impl AutoDocGenerator {
253 #[wasm_bindgen(constructor)]
255 pub fn new(config: DocConfig) -> Self {
256 Self {
257 config,
258 ts_nodes: Vec::new(),
259 examples: Vec::new(),
260 }
261 }
262
263 pub fn parse_typescript(&mut self, typescript_content: &str) -> Result<(), JsValue> {
265 let lines: Vec<&str> = typescript_content.lines().collect();
269 let mut current_line = 0;
270
271 while current_line < lines.len() {
272 let line = lines[current_line].trim();
273
274 if line.starts_with("export interface") {
275 self.parse_interface(&lines, &mut current_line)?;
276 } else if line.starts_with("export class") {
277 self.parse_class(&lines, &mut current_line)?;
278 } else if line.starts_with("export function")
279 || line.starts_with("export declare function")
280 {
281 self.parse_function(&lines, &mut current_line)?;
282 } else if line.starts_with("export enum") {
283 self.parse_enum(&lines, &mut current_line)?;
284 }
285
286 current_line += 1;
287 }
288
289 Ok(())
290 }
291
292 pub fn generate_documentation(&self) -> Result<String, JsValue> {
294 match self.config.format {
295 DocFormat::HTML => self.generate_html(),
296 DocFormat::Markdown => self.generate_markdown(),
297 DocFormat::JSON => self.generate_json(),
298 DocFormat::OpenAPI => self.generate_openapi(),
299 DocFormat::TypeDoc => self.generate_typedoc(),
300 }
301 }
302
303 pub fn add_example(&mut self, example_json: &str) -> Result<(), JsValue> {
305 let example: CodeExample = serde_json::from_str(example_json)
306 .map_err(|e| JsValue::from_str(&format!("Invalid example: {e}")))?;
307
308 self.examples.push(example);
309 Ok(())
310 }
311
312 pub fn generate_navigation(&self) -> String {
314 let nav = self.build_navigation();
315 serde_json::to_string_pretty(&nav).unwrap_or_else(|_| "{}".to_string())
316 }
317
318 pub fn generate_api_reference(&self) -> String {
320 let mut content = String::new();
321
322 let interfaces: Vec<_> = self
324 .ts_nodes
325 .iter()
326 .filter(|n| matches!(n.node_type, TSNodeType::Interface))
327 .collect();
328 let classes: Vec<_> = self
329 .ts_nodes
330 .iter()
331 .filter(|n| matches!(n.node_type, TSNodeType::Class))
332 .collect();
333 let functions: Vec<_> = self
334 .ts_nodes
335 .iter()
336 .filter(|n| matches!(n.node_type, TSNodeType::Function))
337 .collect();
338 let enums: Vec<_> = self
339 .ts_nodes
340 .iter()
341 .filter(|n| matches!(n.node_type, TSNodeType::Enum))
342 .collect();
343
344 if !interfaces.is_empty() {
346 content.push_str("# Interfaces\n\n");
347 for interface in interfaces {
348 content.push_str(&self.format_interface_doc(interface));
349 }
350 }
351
352 if !classes.is_empty() {
353 content.push_str("# Classes\n\n");
354 for class in classes {
355 content.push_str(&self.format_class_doc(class));
356 }
357 }
358
359 if !functions.is_empty() {
360 content.push_str("# Functions\n\n");
361 for function in functions {
362 content.push_str(&self.format_function_doc(function));
363 }
364 }
365
366 if !enums.is_empty() {
367 content.push_str("# Enumerations\n\n");
368 for enum_node in enums {
369 content.push_str(&self.format_enum_doc(enum_node));
370 }
371 }
372
373 content
374 }
375
376 pub fn generate_playground(&self) -> String {
378 if !self.config.generate_playground {
379 return "Playground generation disabled".to_string();
380 }
381
382 let mut playground = String::new();
383
384 playground.push_str(r#"
385<!DOCTYPE html>
386<html lang="en">
387<head>
388 <meta charset="UTF-8">
389 <meta name="viewport" content="width=device-width, initial-scale=1.0">
390 <title>TrustFormer WASM Playground</title>
391 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
392 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css">
393 <style>
394 body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
395 .playground { display: flex; height: 100vh; }
396 .editor-panel { flex: 1; display: flex; flex-direction: column; }
397 .output-panel { flex: 1; display: flex; flex-direction: column; border-left: 1px solid #ccc; }
398 .toolbar { padding: 10px; background: #f5f5f5; border-bottom: 1px solid #ccc; }
399 .editor { flex: 1; }
400 .output { flex: 1; padding: 10px; overflow: auto; background: #1e1e1e; color: #fff; font-family: monospace; }
401 button { padding: 8px 16px; margin-right: 8px; }
402 select { padding: 8px; margin-right: 8px; }
403 </style>
404</head>
405<body>
406 <div class="playground">
407 <div class="editor-panel">
408 <div class="toolbar">
409 <select id="example-select">
410 <option value="">Select an example...</option>
411"#);
412
413 for (i, example) in self.examples.iter().enumerate() {
415 playground.push_str(&format!(
416 r#" <option value="{}">{}</option>
417"#,
418 i, example.title
419 ));
420 }
421
422 playground.push_str(r#"
423 </select>
424 <button onclick="runCode()">Run</button>
425 <button onclick="clearOutput()">Clear</button>
426 </div>
427 <div class="editor">
428 <textarea id="code-editor"></textarea>
429 </div>
430 </div>
431 <div class="output-panel">
432 <div class="toolbar">
433 <strong>Output</strong>
434 </div>
435 <div class="output" id="output"></div>
436 </div>
437 </div>
438
439 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
440 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
441 <script>
442 const examples = [
443"#);
444
445 for example in &self.examples {
447 playground.push_str(&format!(
448 r#" {{
449 title: "{}",
450 description: "{}",
451 code: `{}`
452 }},
453"#,
454 example.title,
455 example.description,
456 example.code.replace('`', "\\`")
457 ));
458 }
459
460 playground.push_str(r#"
461 ];
462
463 const editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), {
464 mode: 'javascript',
465 theme: 'monokai',
466 lineNumbers: true,
467 autoCloseBrackets: true,
468 matchBrackets: true
469 });
470
471 document.getElementById('example-select').addEventListener('change', function(e) {
472 const index = parseInt(e.target.value);
473 if (!isNaN(index) && examples[index]) {
474 editor.setValue(examples[index].code);
475 }
476 });
477
478 function runCode() {
479 const code = editor.getValue();
480 const output = document.getElementById('output');
481
482 try {
483 // This would execute the code with the WASM module
484 output.innerHTML += '> ' + code + '\n';
485 output.innerHTML += 'Note: This is a demo playground. Real execution would require the WASM module.\n\n';
486 output.scrollTop = output.scrollHeight;
487 } catch (error) {
488 output.innerHTML += 'Error: ' + error.message + '\n\n';
489 output.scrollTop = output.scrollHeight;
490 }
491 }
492
493 function clearOutput() {
494 document.getElementById('output').innerHTML = '';
495 }
496
497 // Load first example by default
498 if (examples.length > 0) {
499 editor.setValue(examples[0].code);
500 }
501 </script>
502</body>
503</html>
504"#);
505
506 playground
507 }
508
509 pub fn get_statistics(&self) -> String {
511 let interface_count = self
512 .ts_nodes
513 .iter()
514 .filter(|n| matches!(n.node_type, TSNodeType::Interface))
515 .count();
516 let class_count = self
517 .ts_nodes
518 .iter()
519 .filter(|n| matches!(n.node_type, TSNodeType::Class))
520 .count();
521 let function_count = self
522 .ts_nodes
523 .iter()
524 .filter(|n| matches!(n.node_type, TSNodeType::Function))
525 .count();
526 let enum_count =
527 self.ts_nodes.iter().filter(|n| matches!(n.node_type, TSNodeType::Enum)).count();
528
529 format!(
530 r#"{{
531 "total_nodes": {},
532 "interfaces": {},
533 "classes": {},
534 "functions": {},
535 "enums": {},
536 "examples": {}
537}}"#,
538 self.ts_nodes.len(),
539 interface_count,
540 class_count,
541 function_count,
542 enum_count,
543 self.examples.len()
544 )
545 }
546
547 fn parse_interface(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
550 let line = lines[*current_line];
551
552 let name = self.extract_name_from_declaration(line, "interface")?;
554
555 let mut properties = Vec::new();
556 *current_line += 1;
557
558 while *current_line < lines.len() {
560 let prop_line = lines[*current_line].trim();
561 if prop_line == "}" {
562 break;
563 }
564
565 if !prop_line.is_empty() && !prop_line.starts_with("//") {
566 if let Ok(property) = self.parse_property(prop_line) {
567 properties.push(property);
568 }
569 }
570
571 *current_line += 1;
572 }
573
574 let node = TSNode {
575 name,
576 node_type: TSNodeType::Interface,
577 description: self
578 .extract_description_comment(lines, *current_line - properties.len() - 1),
579 parameters: Vec::new(),
580 return_type: None,
581 properties,
582 examples: Vec::new(),
583 deprecated: false,
584 since_version: None,
585 file_path: "parsed.ts".to_string(),
586 line_number: *current_line as u32,
587 };
588
589 self.ts_nodes.push(node);
590 Ok(())
591 }
592
593 fn parse_class(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
594 let line = lines[*current_line];
595 let name = self.extract_name_from_declaration(line, "class")?;
596
597 let node = TSNode {
600 name,
601 node_type: TSNodeType::Class,
602 description: self.extract_description_comment(lines, *current_line - 1),
603 parameters: Vec::new(),
604 return_type: None,
605 properties: Vec::new(),
606 examples: Vec::new(),
607 deprecated: false,
608 since_version: None,
609 file_path: "parsed.ts".to_string(),
610 line_number: *current_line as u32,
611 };
612
613 self.ts_nodes.push(node);
614 Ok(())
615 }
616
617 fn parse_function(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
618 let line = lines[*current_line];
619 let name = self.extract_name_from_declaration(line, "function")?;
620
621 let (parameters, return_type) = self.parse_function_signature(line)?;
623
624 let node = TSNode {
625 name,
626 node_type: TSNodeType::Function,
627 description: self.extract_description_comment(lines, *current_line - 1),
628 parameters,
629 return_type,
630 properties: Vec::new(),
631 examples: Vec::new(),
632 deprecated: false,
633 since_version: None,
634 file_path: "parsed.ts".to_string(),
635 line_number: *current_line as u32,
636 };
637
638 self.ts_nodes.push(node);
639 Ok(())
640 }
641
642 fn parse_enum(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
643 let line = lines[*current_line];
644 let name = self.extract_name_from_declaration(line, "enum")?;
645
646 let node = TSNode {
647 name,
648 node_type: TSNodeType::Enum,
649 description: self.extract_description_comment(lines, *current_line - 1),
650 parameters: Vec::new(),
651 return_type: None,
652 properties: Vec::new(),
653 examples: Vec::new(),
654 deprecated: false,
655 since_version: None,
656 file_path: "parsed.ts".to_string(),
657 line_number: *current_line as u32,
658 };
659
660 self.ts_nodes.push(node);
661 Ok(())
662 }
663
664 fn extract_name_from_declaration(&self, line: &str, keyword: &str) -> Result<String, JsValue> {
665 let parts: Vec<&str> = line.split_whitespace().collect();
666
667 for i in 0..parts.len() {
668 if parts[i] == keyword && i + 1 < parts.len() {
669 let name = parts[i + 1];
670 let clean_name =
672 name.split('<').next().unwrap_or(name).split('(').next().unwrap_or(name);
673 return Ok(clean_name.to_string());
674 }
675 }
676
677 Err(JsValue::from_str(&format!(
678 "Could not extract name from {} declaration",
679 keyword
680 )))
681 }
682
683 fn parse_property(&self, line: &str) -> Result<TSProperty, JsValue> {
684 let parts: Vec<&str> = line.split(':').collect();
686 if parts.len() != 2 {
687 return Err(JsValue::from_str("Invalid property syntax"));
688 }
689
690 let name_part = parts[0].trim();
691 let type_part = parts[1].trim().trim_end_matches(';');
692
693 let optional = name_part.ends_with('?');
694 let readonly = name_part.starts_with("readonly ");
695
696 let name = name_part
697 .trim_start_matches("readonly ")
698 .trim_end_matches('?')
699 .trim()
700 .to_string();
701
702 Ok(TSProperty {
703 name,
704 property_type: type_part.to_string(),
705 optional,
706 readonly,
707 description: None,
708 })
709 }
710
711 fn parse_function_signature(
712 &self,
713 _line: &str,
714 ) -> Result<(Vec<TSParameter>, Option<String>), JsValue> {
715 let parameters = Vec::new(); let return_type = Some("any".to_string()); Ok((parameters, return_type))
720 }
721
722 fn extract_description_comment(&self, lines: &[&str], line_index: usize) -> Option<String> {
723 if line_index == 0 {
724 return None;
725 }
726
727 let comment_line = lines[line_index - 1].trim();
728 if comment_line.starts_with("//") {
729 Some(comment_line.trim_start_matches("//").trim().to_string())
730 } else {
731 None
732 }
733 }
734
735 fn generate_html(&self) -> Result<String, JsValue> {
738 let mut html = String::new();
739
740 html.push_str(&format!(
741 r#"
742<!DOCTYPE html>
743<html lang="en">
744<head>
745 <meta charset="UTF-8">
746 <meta name="viewport" content="width=device-width, initial-scale=1.0">
747 <title>{}</title>
748 {}
749</head>
750<body>
751 <div class="container">
752 <header>
753 <h1>{}</h1>
754 <p>Version: {}</p>
755 </header>
756 <nav>
757 {}
758 </nav>
759 <main>
760 {}
761 </main>
762 </div>
763 {}
764</body>
765</html>
766"#,
767 self.config.title,
768 self.get_html_styles(),
769 self.config.title,
770 self.config.version,
771 self.generate_html_navigation(),
772 self.generate_api_reference(),
773 self.get_html_scripts()
774 ));
775
776 Ok(html)
777 }
778
779 fn generate_markdown(&self) -> Result<String, JsValue> {
780 let mut md = String::new();
781
782 md.push_str(&format!("# {title}\n\n", title = self.config.title));
783 md.push_str(&format!(
784 "Version: {version}\n\n",
785 version = self.config.version
786 ));
787 md.push_str(&self.generate_api_reference());
788
789 Ok(md)
790 }
791
792 fn generate_json(&self) -> Result<String, JsValue> {
793 let doc = GeneratedDoc {
794 title: self.config.title.clone(),
795 version: self.config.version.clone(),
796 generated_at: js_sys::Date::now(),
797 format: self.config.format,
798 content: self.generate_api_reference(),
799 assets: Vec::new(),
800 navigation: self.build_navigation(),
801 };
802
803 serde_json::to_string_pretty(&doc).map_err(|e| JsValue::from_str(&e.to_string()))
804 }
805
806 fn generate_openapi(&self) -> Result<String, JsValue> {
807 let mut spec = String::new();
809 spec.push_str("openapi: 3.0.0\n");
810 spec.push_str(&format!(
811 "info:\n title: {}\n version: {}\n",
812 self.config.title, self.config.version
813 ));
814 spec.push_str("paths:\n");
815
816 for node in &self.ts_nodes {
818 if matches!(node.node_type, TSNodeType::Function) {
819 spec.push_str(&format!(
820 " /{}:\n post:\n summary: {}\n",
821 node.name,
822 node.description.as_deref().unwrap_or(&node.name)
823 ));
824 }
825 }
826
827 Ok(spec)
828 }
829
830 fn generate_typedoc(&self) -> Result<String, JsValue> {
831 self.generate_json()
833 }
834
835 fn build_navigation(&self) -> DocNavigation {
836 let mut sections = Vec::new();
837
838 let interface_items: Vec<_> = self
840 .ts_nodes
841 .iter()
842 .filter(|n| matches!(n.node_type, TSNodeType::Interface))
843 .map(|n| NavItem {
844 title: n.name.clone(),
845 link: format!("#{name}", name = n.name.to_lowercase()),
846 children: Vec::new(),
847 })
848 .collect();
849
850 if !interface_items.is_empty() {
851 sections.push(NavSection {
852 title: "Interfaces".to_string(),
853 items: interface_items,
854 });
855 }
856
857 let class_items: Vec<_> = self
858 .ts_nodes
859 .iter()
860 .filter(|n| matches!(n.node_type, TSNodeType::Class))
861 .map(|n| NavItem {
862 title: n.name.clone(),
863 link: format!("#{name}", name = n.name.to_lowercase()),
864 children: Vec::new(),
865 })
866 .collect();
867
868 if !class_items.is_empty() {
869 sections.push(NavSection {
870 title: "Classes".to_string(),
871 items: class_items,
872 });
873 }
874
875 DocNavigation { sections }
876 }
877
878 fn format_interface_doc(&self, interface: &TSNode) -> String {
879 let mut doc = String::new();
880 doc.push_str(&format!("## {name}\n\n", name = interface.name));
881
882 if let Some(ref description) = interface.description {
883 doc.push_str(&format!("{description}\n\n"));
884 }
885
886 if !interface.properties.is_empty() {
887 doc.push_str("### Properties\n\n");
888 for prop in &interface.properties {
889 doc.push_str(&format!(
890 "- **{}**{}: `{}` {}\n",
891 prop.name,
892 if prop.optional { "?" } else { "" },
893 prop.property_type,
894 if prop.readonly { "(readonly)" } else { "" }
895 ));
896
897 if let Some(ref desc) = prop.description {
898 doc.push_str(&format!(" {desc}\n"));
899 }
900 }
901 doc.push('\n');
902 }
903
904 doc
905 }
906
907 fn format_class_doc(&self, class: &TSNode) -> String {
908 let mut doc = String::new();
909 doc.push_str(&format!("## {name}\n\n", name = class.name));
910
911 if let Some(ref description) = class.description {
912 doc.push_str(&format!("{description}\n\n"));
913 }
914
915 doc
916 }
917
918 fn format_function_doc(&self, function: &TSNode) -> String {
919 let mut doc = String::new();
920 doc.push_str(&format!("## {name}\n\n", name = function.name));
921
922 if let Some(ref description) = function.description {
923 doc.push_str(&format!("{description}\n\n"));
924 }
925
926 if !function.parameters.is_empty() {
927 doc.push_str("### Parameters\n\n");
928 for param in &function.parameters {
929 doc.push_str(&format!(
930 "- **{}**{}: `{}`\n",
931 param.name,
932 if param.optional { "?" } else { "" },
933 param.param_type
934 ));
935
936 if let Some(ref desc) = param.description {
937 doc.push_str(&format!(" {desc}\n"));
938 }
939 }
940 doc.push('\n');
941 }
942
943 if let Some(ref return_type) = function.return_type {
944 doc.push_str(&format!("### Returns\n\n`{return_type}`\n\n"));
945 }
946
947 doc
948 }
949
950 fn format_enum_doc(&self, enum_node: &TSNode) -> String {
951 let mut doc = String::new();
952 doc.push_str(&format!("## {name}\n\n", name = enum_node.name));
953
954 if let Some(ref description) = enum_node.description {
955 doc.push_str(&format!("{description}\n\n"));
956 }
957
958 doc
959 }
960
961 fn generate_html_navigation(&self) -> String {
962 let nav = self.build_navigation();
963 let mut html = String::new();
964
965 html.push_str("<ul>");
966 for section in &nav.sections {
967 html.push_str(&format!(
968 "<li><strong>{title}</strong><ul>",
969 title = section.title
970 ));
971 for item in §ion.items {
972 html.push_str(&format!(
973 "<li><a href=\"{}\">{}</a></li>",
974 item.link, item.title
975 ));
976 }
977 html.push_str("</ul></li>");
978 }
979 html.push_str("</ul>");
980
981 html
982 }
983
984 fn get_html_styles(&self) -> String {
985 match self.config.theme {
986 DocTheme::Default => self.get_default_styles(),
987 DocTheme::Dark => self.get_dark_styles(),
988 DocTheme::Material => self.get_material_styles(),
989 DocTheme::Bootstrap => self.get_bootstrap_styles(),
990 DocTheme::Minimal => self.get_minimal_styles(),
991 }
992 }
993
994 fn get_default_styles(&self) -> String {
995 r#"
996<style>
997 body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; background: #fff; }
998 .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
999 header { text-align: center; margin-bottom: 2rem; }
1000 nav { background: #f8f9fa; padding: 1rem; border-radius: 8px; margin-bottom: 2rem; }
1001 nav ul { list-style: none; padding: 0; }
1002 nav li { margin: 0.5rem 0; }
1003 nav a { text-decoration: none; color: #0366d6; }
1004 h1, h2, h3 { color: #24292e; }
1005 code { background: #f6f8fa; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1006 pre { background: #f6f8fa; padding: 1rem; border-radius: 6px; overflow-x: auto; }
1007</style>
1008"#.to_string()
1009 }
1010
1011 fn get_dark_styles(&self) -> String {
1012 r#"
1013<style>
1014 body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; background: #0d1117; color: #c9d1d9; }
1015 .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
1016 header { text-align: center; margin-bottom: 2rem; }
1017 nav { background: #161b22; padding: 1rem; border-radius: 8px; margin-bottom: 2rem; border: 1px solid #30363d; }
1018 nav ul { list-style: none; padding: 0; }
1019 nav li { margin: 0.5rem 0; }
1020 nav a { text-decoration: none; color: #58a6ff; }
1021 h1, h2, h3 { color: #f0f6fc; }
1022 code { background: #161b22; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1023 pre { background: #161b22; padding: 1rem; border-radius: 6px; overflow-x: auto; border: 1px solid #30363d; }
1024</style>
1025"#.to_string()
1026 }
1027
1028 fn get_material_styles(&self) -> String {
1029 r#"
1030<style>
1031 body { font-family: 'Roboto', sans-serif; margin: 0; background: #fafafa; }
1032 .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
1033 header { text-align: center; margin-bottom: 2rem; background: #2196f3; color: white; padding: 2rem; border-radius: 4px; }
1034 nav { background: white; padding: 1rem; border-radius: 4px; margin-bottom: 2rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
1035 nav ul { list-style: none; padding: 0; }
1036 nav li { margin: 0.5rem 0; }
1037 nav a { text-decoration: none; color: #2196f3; }
1038 h1, h2, h3 { color: #212121; }
1039 code { background: #e8eaf6; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1040 pre { background: #e8eaf6; padding: 1rem; border-radius: 4px; overflow-x: auto; }
1041</style>
1042"#.to_string()
1043 }
1044
1045 fn get_bootstrap_styles(&self) -> String {
1046 r#"
1047<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
1048<style>
1049 .container { margin-top: 2rem; }
1050 nav { background: #f8f9fa; padding: 1rem; border-radius: 0.375rem; margin-bottom: 2rem; }
1051 code { color: #e83e8c; }
1052</style>
1053"#.to_string()
1054 }
1055
1056 fn get_minimal_styles(&self) -> String {
1057 r#"
1058<style>
1059 body { font-family: Georgia, serif; margin: 0; background: white; line-height: 1.6; }
1060 .container { max-width: 800px; margin: 0 auto; padding: 20px; }
1061 header { margin-bottom: 2rem; }
1062 nav { margin-bottom: 2rem; }
1063 nav ul { list-style: none; padding: 0; }
1064 nav a { text-decoration: none; color: #000; border-bottom: 1px dotted; }
1065 h1, h2, h3 { font-weight: normal; }
1066 code { font-family: 'Courier New', monospace; }
1067</style>
1068"#
1069 .to_string()
1070 }
1071
1072 fn get_html_scripts(&self) -> String {
1073 r##"
1074<script>
1075 // Add interactive features
1076 document.addEventListener('DOMContentLoaded', function() {
1077 // Smooth scrolling for navigation links
1078 const links = document.querySelectorAll('nav a[href^="#"]');
1079 links.forEach(link => {
1080 link.addEventListener('click', function(e) {
1081 e.preventDefault();
1082 const target = document.querySelector(this.getAttribute('href'));
1083 if (target) {
1084 target.scrollIntoView({ behavior: 'smooth' });
1085 }
1086 });
1087 });
1088 });
1089</script>
1090"##
1091 .to_string()
1092 }
1093}
1094
1095#[wasm_bindgen]
1097pub fn create_default_doc_generator() -> AutoDocGenerator {
1098 AutoDocGenerator::new(DocConfig::new())
1099}
1100
1101#[wasm_bindgen]
1103pub fn create_html_doc_generator() -> AutoDocGenerator {
1104 let mut config = DocConfig::new();
1105 config.set_format(DocFormat::HTML);
1106 AutoDocGenerator::new(config)
1107}
1108
1109#[wasm_bindgen]
1111pub fn create_markdown_doc_generator() -> AutoDocGenerator {
1112 let mut config = DocConfig::new();
1113 config.set_format(DocFormat::Markdown);
1114 AutoDocGenerator::new(config)
1115}
1116
1117#[wasm_bindgen]
1119#[derive(Debug, Clone, Serialize, Deserialize)]
1120pub struct VersionInfo {
1121 version: String,
1123 git_hash: String,
1125 target: String,
1127 profile: String,
1129 build_time: String,
1131 features: Vec<String>,
1133}
1134
1135#[wasm_bindgen]
1136impl VersionInfo {
1137 #[wasm_bindgen(getter)]
1138 pub fn version(&self) -> String {
1139 self.version.clone()
1140 }
1141
1142 #[wasm_bindgen(getter)]
1143 pub fn git_hash(&self) -> String {
1144 self.git_hash.clone()
1145 }
1146
1147 #[wasm_bindgen(getter)]
1148 pub fn target(&self) -> String {
1149 self.target.clone()
1150 }
1151
1152 #[wasm_bindgen(getter)]
1153 pub fn profile(&self) -> String {
1154 self.profile.clone()
1155 }
1156
1157 #[wasm_bindgen(getter)]
1158 pub fn build_time(&self) -> String {
1159 self.build_time.clone()
1160 }
1161
1162 #[wasm_bindgen(getter)]
1163 pub fn features(&self) -> Array {
1164 self.features.iter().map(|f| JsValue::from(f.as_str())).collect()
1165 }
1166}
1167
1168#[wasm_bindgen]
1170pub fn get_version_info() -> VersionInfo {
1171 VersionInfo {
1172 version: env!("CARGO_PKG_VERSION").to_string(),
1173 git_hash: option_env!("GIT_HASH").unwrap_or("unknown").to_string(),
1174 target: std::env::consts::ARCH.to_string() + "-" + std::env::consts::OS,
1175 profile: if cfg!(debug_assertions) { "debug".to_string() } else { "release".to_string() },
1176 build_time: option_env!("BUILD_TIME").unwrap_or("unknown").to_string(),
1177 features: get_enabled_features(),
1178 }
1179}
1180
1181#[allow(clippy::vec_init_then_push)] fn get_enabled_features() -> Vec<String> {
1184 let mut features = Vec::new();
1185
1186 #[cfg(feature = "console_panic")]
1187 features.push("console_panic".to_string());
1188
1189 #[cfg(feature = "webgpu")]
1190 features.push("webgpu".to_string());
1191
1192 #[cfg(feature = "web-workers")]
1193 features.push("web-workers".to_string());
1194
1195 #[cfg(feature = "shared-memory")]
1196 features.push("shared-memory".to_string());
1197
1198 #[cfg(feature = "kernel-fusion")]
1199 features.push("kernel-fusion".to_string());
1200
1201 #[cfg(feature = "async-executor")]
1202 features.push("async-executor".to_string());
1203
1204 #[cfg(feature = "indexeddb")]
1205 features.push("indexeddb".to_string());
1206
1207 #[cfg(feature = "memory64")]
1208 features.push("memory64".to_string());
1209
1210 #[cfg(feature = "streaming-loader")]
1211 features.push("streaming-loader".to_string());
1212
1213 #[cfg(feature = "model-splitting")]
1214 features.push("model-splitting".to_string());
1215
1216 #[cfg(feature = "react-components")]
1217 features.push("react-components".to_string());
1218
1219 #[cfg(feature = "vue-components")]
1220 features.push("vue-components".to_string());
1221
1222 #[cfg(feature = "angular-components")]
1223 features.push("angular-components".to_string());
1224
1225 #[cfg(feature = "web-components")]
1226 features.push("web-components".to_string());
1227
1228 #[cfg(feature = "playground")]
1229 features.push("playground".to_string());
1230
1231 #[cfg(feature = "streaming-generation")]
1232 features.push("streaming-generation".to_string());
1233
1234 #[cfg(feature = "mobile-optimization")]
1235 features.push("mobile-optimization".to_string());
1236
1237 #[cfg(feature = "dlmalloc-alloc")]
1238 features.push("dlmalloc-alloc".to_string());
1239
1240 features
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use super::*;
1246
1247 #[test]
1248 fn test_doc_config() {
1249 let mut config = DocConfig::new();
1250 assert_eq!(config.format, DocFormat::HTML);
1251 assert_eq!(config.theme, DocTheme::Default);
1252
1253 config.set_format(DocFormat::Markdown);
1254 assert_eq!(config.format, DocFormat::Markdown);
1255 }
1256
1257 #[test]
1258 fn test_typescript_parsing() {
1259 let mut generator = AutoDocGenerator::new(DocConfig::new());
1260
1261 let typescript = r#"
1262export interface TestInterface {
1263 name: string;
1264 optional?: number;
1265}
1266 "#;
1267
1268 let result = generator.parse_typescript(typescript);
1269 assert!(result.is_ok());
1270 assert_eq!(generator.ts_nodes.len(), 1);
1271 assert_eq!(generator.ts_nodes[0].name, "TestInterface");
1272 }
1273
1274 #[test]
1275 fn test_documentation_generation() {
1276 let generator = AutoDocGenerator::new(DocConfig::new());
1277 let doc = generator.generate_documentation();
1278 assert!(doc.is_ok());
1279 }
1280}