chore: refactored natspec and parsing 08dc095e
Steve · 2025-08-31 12:37 4 file(s) · +93 −125
client/src/App.tsx +3 −114
3 3
import { Marked } from 'marked';
4 4
import { markedUiExtension } from '@markdown-ui/marked-ext';
5 5
import '@markdown-ui/react/widgets.css';
6 +
import { parseContractToMarkdown, type ContractResponse } from './utils/contractParser';
6 7
7 8
const marked = new Marked().use(markedUiExtension);
8 9
9 -
const CONTRACT_ADDRESS = "0xF7eb390231F0Db11C673390f3C25e613D7228659";
10 +
const CONTRACT_ADDRESS = "0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F";
10 11
const CHAIN_ID = "11155111"; // Sepolia
11 12
12 -
interface DevDocMethod {
13 -
  details?: string;
14 -
  params?: Record<string, string>;
15 -
}
16 -
17 -
interface DevDoc {
18 -
  kind?: string;
19 -
  title?: string;
20 -
  details?: string;
21 -
  methods: Record<string, DevDocMethod>;
22 -
  stateVariables?: Record<string, { details: string }>;
23 -
  version?: number;
24 -
}
25 -
26 -
interface ContractResponse {
27 -
  devdoc: DevDoc;
28 -
  matchId?: string;
29 -
  creationMatch?: string;
30 -
  runtimeMatch?: string;
31 -
  verifiedAt?: string;
32 -
  match?: string;
33 -
  chainId?: string;
34 -
  address?: string;
35 -
}
36 -
37 13
function App() {
38 14
  const [contractHtml, setContractHtml] = useState<string>("");
39 15
  const [loading, setLoading] = useState<boolean>(true);
53 29
        
54 30
        const data: ContractResponse = await response.json();
55 31
        
56 -
        let markdownContent = "";
57 -
        
58 -
        // Add contract header information
59 -
        if (data.devdoc?.title) {
60 -
          markdownContent += `# ${data.devdoc.title}\n\n`;
61 -
        }
62 -
        
63 -
        if (data.devdoc?.details) {
64 -
          markdownContent += `${data.devdoc.details}\n\n`;
65 -
        }
66 -
        
67 -
        // Add contract verification info
68 -
        if (data.address && data.chainId) {
69 -
          markdownContent += `**Contract:** \`${data.address}\` on Chain ID \`${data.chainId}\`\n\n`;
70 -
        }
71 -
        
72 -
        if (data.verifiedAt) {
73 -
          markdownContent += `**Verified:** ${new Date(data.verifiedAt).toLocaleDateString()}\n\n`;
74 -
        }
75 -
        
76 -
        // Process methods
77 -
        if (data.devdoc?.methods) {
78 -
          Object.entries(data.devdoc.methods).forEach(([methodName, method]) => {
79 -
            markdownContent += `## ${methodName}\n\n`;
80 -
            
81 -
            // Add method description (without markdown widgets)
82 -
            if (method.details) {
83 -
              const detailsWithoutWidgets = method.details.replace(/```markdown-ui-widget[\s\S]*?```/g, '').trim();
84 -
              if (detailsWithoutWidgets) {
85 -
                markdownContent += `${detailsWithoutWidgets}\n\n`;
86 -
              }
87 -
              
88 -
              // Extract and format markdown widgets
89 -
              const widgetMatches = method.details.match(/```markdown-ui-widget[\s\S]*?```/g);
90 -
              if (widgetMatches) {
91 -
                widgetMatches.forEach(widget => {
92 -
                  // Extract the content between the backticks
93 -
                  const widgetContent = widget.replace(/```markdown-ui-widget\s*/, '').replace(/```$/, '').trim();
94 -
                  
95 -
                  // Try to parse as JSON to validate format
96 -
                  try {
97 -
                    JSON.parse(widgetContent);
98 -
                    // If valid JSON, format it properly
99 -
                    markdownContent += `\`\`\`markdown-ui-widget\n${widgetContent}\n\`\`\`\n\n`;
100 -
                  } catch (e) {
101 -
                    // If not valid JSON, treat as DSL or skip
102 -
                    console.warn('Invalid JSON in widget:', widgetContent);
103 -
                    markdownContent += `\`\`\`markdown-ui-widget\n${widgetContent}\n\`\`\`\n\n`;
104 -
                  }
105 -
                });
106 -
              }
107 -
            }
108 -
            
109 -
            // Add parameter documentation
110 -
            if (method.params) {
111 -
              Object.entries(method.params).forEach(([paramName, paramDesc]) => {
112 -
                markdownContent += `**${paramName}:** `;
113 -
                
114 -
                // Add param description (without markdown widgets)
115 -
                const paramWithoutWidgets = paramDesc.replace(/```markdown-ui-widget[\s\S]*?```/g, '').trim();
116 -
                if (paramWithoutWidgets) {
117 -
                  markdownContent += `${paramWithoutWidgets}\n\n`;
118 -
                }
119 -
                
120 -
                // Extract and format parameter widgets
121 -
                const paramWidgetMatches = paramDesc.match(/```markdown-ui-widget[\s\S]*?```/g);
122 -
                if (paramWidgetMatches) {
123 -
                  paramWidgetMatches.forEach(widget => {
124 -
                    const widgetContent = widget.replace(/```markdown-ui-widget\s*/, '').replace(/```$/, '').trim();
125 -
                    
126 -
                    // Try to parse as JSON to validate format
127 -
                    try {
128 -
                      JSON.parse(widgetContent);
129 -
                      // If valid JSON, format it properly
130 -
                      markdownContent += `\`\`\`markdown-ui-widget\n${widgetContent}\n\`\`\`\n\n`;
131 -
                    } catch (e) {
132 -
                      // If not valid JSON, treat as DSL or skip
133 -
                      console.warn('Invalid JSON in param widget:', widgetContent);
134 -
                      markdownContent += `\`\`\`markdown-ui-widget\n${widgetContent}\n\`\`\`\n\n`;
135 -
                    }
136 -
                  });
137 -
                }
138 -
              });
139 -
            }
140 -
            
141 -
            markdownContent += "\n";
142 -
          });
143 -
        }
32 +
        const markdownContent = parseContractToMarkdown(data);
144 33
        
145 34
        console.log(markdownContent);
146 35
        const html = await marked.parse(markdownContent || '# No markdown widgets found');
client/src/index.css +6 −1
1 -
@import "tailwindcss";
1 +
html {
2 +
  font-family: sans-serif;
3 +
  padding: 2rem 4rem;
4 +
  max-width: 700px;
5 +
  margin: auto;
6 +
}
client/src/utils/contractParser.ts (added) +74 −0
1 +
interface DevDocMethod {
2 +
  details?: string;
3 +
  params?: Record<string, string>;
4 +
}
5 +
6 +
interface DevDoc {
7 +
  kind?: string;
8 +
  title?: string;
9 +
  details?: string;
10 +
  methods: Record<string, DevDocMethod>;
11 +
  stateVariables?: Record<string, { details: string }>;
12 +
  version?: number;
13 +
}
14 +
15 +
interface ContractResponse {
16 +
  devdoc: DevDoc;
17 +
  matchId?: string;
18 +
  creationMatch?: string;
19 +
  runtimeMatch?: string;
20 +
  verifiedAt?: string;
21 +
  match?: string;
22 +
  chainId?: string;
23 +
  address?: string;
24 +
}
25 +
26 +
export function parseContractToMarkdown(data: ContractResponse): string {
27 +
  let markdownContent = "";
28 +
  
29 +
  // Add contract header information
30 +
  if (data.devdoc?.title) {
31 +
    markdownContent += `# ${data.devdoc.title}\n\n`;
32 +
  }
33 +
  
34 +
  if (data.devdoc?.details) {
35 +
    markdownContent += `${data.devdoc.details}\n\n`;
36 +
  }
37 +
  
38 +
  // Add contract verification info
39 +
  if (data.address && data.chainId) {
40 +
    markdownContent += `**Contract:** \`${data.address}\` on Chain ID \`${data.chainId}\`\n\n`;
41 +
  }
42 +
  
43 +
  if (data.verifiedAt) {
44 +
    markdownContent += `**Verified:** ${new Date(data.verifiedAt).toLocaleDateString()}\n\n`;
45 +
  }
46 +
  
47 +
  // Process methods
48 +
  if (data.devdoc?.methods) {
49 +
    Object.entries(data.devdoc.methods).forEach(([methodName, method]) => {
50 +
      markdownContent += `## ${methodName}\n\n`;
51 +
      
52 +
      if (method.details) {
53 +
        // Replace \n with actual newlines
54 +
        const processedDetails = method.details.replace(/\\n/g, '\n');
55 +
        markdownContent += `${processedDetails}\n\n`;
56 +
      }
57 +
      
58 +
      if (method.params) {
59 +
        Object.entries(method.params).forEach(([paramName, paramDesc]) => {
60 +
          markdownContent += `**${paramName}:** `;
61 +
          // Replace \n with actual newlines
62 +
          const processedParamDesc = paramDesc.replace(/\\n/g, '\n');
63 +
          markdownContent += `${processedParamDesc}\n\n`;
64 +
        });
65 +
      }
66 +
      
67 +
      markdownContent += "\n";
68 +
    });
69 +
  }
70 +
  
71 +
  return markdownContent;
72 +
}
73 +
74 +
export type { ContractResponse, DevDoc, DevDocMethod };
contracts/src/Counter.sol +10 −10
11 11
12 12
    /// @notice Sets the counter to a specific value
13 13
    /// @dev Updates the number state variable to the provided value
14 -
    /// @param newNumber The new value to set the counter to
15 -
    /// 
16 -
    /// ```markdown-ui-widget
17 -
    /// { "type": "form", "id": "setNumber", "submitLabel": "Set Number", "fields": [{ "type": "text-input", "id": "newValue", "label": "New Counter Value", "placeholder": "Enter number", "default": "42" }] }
18 -
    /// ```
14 +
    /// @param newNumber The new value to set the counter to \n
15 +
    /// \n 
16 +
    /// ```markdown-ui-widget \n
17 +
    /// { "type": "form", "id": "setNumber", "submitLabel": "Set Number", "fields": [{ "type": "text-input", "id": "newValue", "label": "New Counter Value", "placeholder": "Enter number", "default": "42" }] } \n
18 +
    /// ``` \n
19 19
    function setNumber(uint256 newNumber) public {
20 20
        number = newNumber;
21 21
    }
22 22
23 23
    /// @notice Increments the counter by 1
24 -
    /// @dev Increases the number state variable by 1 using the increment operator
25 -
    /// 
26 -
    /// ```markdown-ui-widget
27 -
    /// { "type": "form", "id": "increment", "submitLabel": "Increment", "fields": [] }
28 -
    /// ```
24 +
    /// @dev Increases the number state variable by 1 using the increment operator \n
25 +
    /// \n
26 +
    /// ```markdown-ui-widget \n
27 +
    /// { "type": "form", "id": "increment", "submitLabel": "Increment", "fields": [] } \n
28 +
    /// ```\n
29 29
    function increment() public {
30 30
        number++;
31 31
    }