1
+ const fs = require ( 'fs' ) ;
2
+ const path = require ( 'path' ) ;
3
+ const glob = require ( 'glob' ) ;
4
+ const { TSDocParser } = require ( '@microsoft/tsdoc' ) ;
5
+ const ts = require ( 'typescript' ) ;
6
+
7
+ /**
8
+ * Standalone function to process docoff-function-doc elements in HTML files
9
+ * and replace them with static HTML content
10
+ */
11
+ async function processDocoffFunctionDoc ( options = { } ) {
12
+ const {
13
+ sourceDir = 'public' ,
14
+ outputDir = 'public' ,
15
+ htmlPattern = '**/*.html' ,
16
+ } = options ;
17
+
18
+ console . log ( 'Processing docoff-function-doc elements...' ) ;
19
+
20
+ // Find all HTML files to process
21
+ const htmlFiles = glob . sync ( path . join ( sourceDir , htmlPattern ) ) ;
22
+
23
+ for ( const htmlFile of htmlFiles ) {
24
+ console . log ( `Processing ${ htmlFile } ...` ) ;
25
+
26
+ let content = fs . readFileSync ( htmlFile , 'utf-8' ) ;
27
+ let hasChanges = false ;
28
+
29
+ // Find all docoff-function-doc elements
30
+ const regex = / < d o c o f f - f u n c t i o n - d o c \s + s r c = " ( [ ^ " ] + ) " > < \/ d o c o f f - f u n c t i o n - d o c > / g;
31
+ let match ;
32
+
33
+ while ( ( match = regex . exec ( content ) ) !== null ) {
34
+ const srcAttribute = match [ 1 ] ;
35
+ const [ filePath , functionName ] = srcAttribute . split ( ':' ) ;
36
+
37
+ if ( filePath && functionName ) {
38
+ try {
39
+ const htmlContent = await generateFunctionDoc ( filePath , functionName , path . dirname ( htmlFile ) ) ;
40
+ content = content . replace ( match [ 0 ] , htmlContent ) ;
41
+ hasChanges = true ;
42
+ console . log ( ` Replaced docoff-function-doc for ${ srcAttribute } ` ) ;
43
+ } catch ( error ) {
44
+ console . warn ( ` Warning: Failed to process docoff-function-doc for ${ srcAttribute } : ${ error . message } ` ) ;
45
+ // Replace with error message
46
+ const errorHtml = `<div style="color: red;">Error loading function documentation: ${ error . message } </div>` ;
47
+ content = content . replace ( match [ 0 ] , errorHtml ) ;
48
+ hasChanges = true ;
49
+ }
50
+ }
51
+ }
52
+
53
+ if ( hasChanges ) {
54
+ // Calculate output path
55
+ const outputPath = path . resolve ( outputDir , path . relative ( sourceDir , htmlFile ) ) ;
56
+
57
+ // Ensure output directory exists
58
+ fs . mkdirSync ( path . dirname ( outputPath ) , { recursive : true } ) ;
59
+
60
+ // Write the updated content
61
+ fs . writeFileSync ( outputPath , content , 'utf-8' ) ;
62
+ console . log ( ` Updated ${ outputPath } ` ) ;
63
+ }
64
+ }
65
+
66
+ console . log ( 'Finished processing docoff-function-doc elements.' ) ;
67
+ }
68
+
69
+ async function generateFunctionDoc ( filePath , functionName , baseDir ) {
70
+ // Resolve the absolute path to the TypeScript file
71
+ const fullPath = path . resolve ( baseDir , filePath . replace ( / ^ \/ / , '' ) ) ;
72
+
73
+ if ( ! fs . existsSync ( fullPath ) ) {
74
+ throw new Error ( `File not found: ${ fullPath } ` ) ;
75
+ }
76
+
77
+ const fileContent = fs . readFileSync ( fullPath , 'utf-8' ) ;
78
+
79
+ // Parse TypeScript file
80
+ const sourceFile = ts . createSourceFile (
81
+ fullPath ,
82
+ fileContent ,
83
+ ts . ScriptTarget . Latest ,
84
+ true
85
+ ) ;
86
+
87
+ // Find the specific function
88
+ const functionNode = findFunctionNode ( sourceFile , functionName ) ;
89
+
90
+ if ( ! functionNode ) {
91
+ throw new Error ( `Function '${ functionName } ' not found in ${ filePath } ` ) ;
92
+ }
93
+
94
+ // Extract TSDoc comment
95
+ const tsdocComment = extractTSDocComment ( sourceFile , functionNode ) ;
96
+
97
+ if ( ! tsdocComment ) {
98
+ throw new Error ( `No TSDoc comment found for function '${ functionName } '` ) ;
99
+ }
100
+
101
+ // Parse TSDoc comment
102
+ const parser = new TSDocParser ( ) ;
103
+ const parserContext = parser . parseString ( tsdocComment ) ;
104
+
105
+ if ( parserContext . log . messages . length > 0 ) {
106
+ console . warn ( `TSDoc parsing warnings for ${ functionName } :` , parserContext . log . messages ) ;
107
+ }
108
+
109
+ // Generate HTML from parsed TSDoc
110
+ return generateHTMLFromTSDoc ( functionName , parserContext . docComment ) ;
111
+ }
112
+
113
+ function findFunctionNode ( sourceFile , functionName ) {
114
+ let functionNode = null ;
115
+
116
+ const visit = ( node ) => {
117
+ if ( ts . isFunctionDeclaration ( node ) && node . name && node . name . text === functionName ) {
118
+ functionNode = node ;
119
+ return ;
120
+ }
121
+
122
+ if ( ts . isVariableStatement ( node ) ) {
123
+ for ( const declaration of node . declarationList . declarations ) {
124
+ if ( ts . isIdentifier ( declaration . name ) && declaration . name . text === functionName ) {
125
+ if ( declaration . initializer &&
126
+ ( ts . isFunctionExpression ( declaration . initializer ) ||
127
+ ts . isArrowFunction ( declaration . initializer ) ) ) {
128
+ functionNode = node ;
129
+ return ;
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ ts . forEachChild ( node , visit ) ;
136
+ } ;
137
+
138
+ visit ( sourceFile ) ;
139
+ return functionNode ;
140
+ }
141
+
142
+ function extractTSDocComment ( sourceFile , functionNode ) {
143
+ // Get leading trivia (comments) for the function node
144
+ const leadingTrivia = functionNode . getFullText ( ) . substring ( 0 , functionNode . getLeadingTriviaWidth ( ) ) ;
145
+
146
+ // Look for TSDoc comment (/** ... */)
147
+ const tsdocRegex = / \/ \* \* [ \s \S ] * ?\* \/ / g;
148
+ const matches = leadingTrivia . match ( tsdocRegex ) ;
149
+
150
+ if ( matches && matches . length > 0 ) {
151
+ // Return the last (closest) TSDoc comment
152
+ return matches [ matches . length - 1 ] ;
153
+ }
154
+
155
+ return null ;
156
+ }
157
+
158
+ function generateHTMLFromTSDoc ( functionName , docComment ) {
159
+ const summary = docComment . summarySection ;
160
+ const params = docComment . params ;
161
+ const returnsBlock = docComment . returnsBlock ;
162
+
163
+ let html = '<dl>' ;
164
+
165
+ // Function name and description
166
+ html += `<dt><strong>${ functionName } </strong></dt>` ;
167
+
168
+ if ( summary && summary . nodes . length > 0 ) {
169
+ const description = extractTextFromNodes ( summary . nodes ) ;
170
+ html += `<dd>${ description } </dd>` ;
171
+ }
172
+
173
+ // Parameters
174
+ if ( params . blocks . length > 0 ) {
175
+ for ( const param of params . blocks ) {
176
+ const paramName = param . parameterName ;
177
+ const paramDescription = param . content ? extractTextFromNodes ( param . content . nodes ) : '' ;
178
+ html += `<dt>Parameter: <code>${ paramName } </code></dt>` ;
179
+ html += `<dd>${ paramDescription } </dd>` ;
180
+ }
181
+ }
182
+
183
+ // Returns
184
+ if ( returnsBlock && returnsBlock . content ) {
185
+ const returnDescription = extractTextFromNodes ( returnsBlock . content . nodes ) ;
186
+ html += `<dt>Returns:</dt>` ;
187
+ html += `<dd>${ returnDescription } </dd>` ;
188
+ }
189
+
190
+ html += '</dl>' ;
191
+
192
+ return html ;
193
+ }
194
+
195
+ function extractTextFromNodes ( nodes ) {
196
+ return nodes . map ( node => {
197
+ if ( node . kind === 'PlainText' ) {
198
+ return node . text ;
199
+ } else if ( node . kind === 'Paragraph' ) {
200
+ return extractTextFromNodes ( node . nodes ) ;
201
+ } else if ( node . kind === 'CodeSpan' ) {
202
+ return `<code>${ node . code } </code>` ;
203
+ }
204
+ return '' ;
205
+ } ) . join ( '' ) . trim ( ) ;
206
+ }
207
+
208
+ module . exports = {
209
+ processDocoffFunctionDoc,
210
+ generateFunctionDoc,
211
+ } ;
212
+
213
+ // If called directly from command line
214
+ if ( require . main === module ) {
215
+ processDocoffFunctionDoc ( ) . catch ( console . error ) ;
216
+ }
0 commit comments