1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | import FileWriter from './FileWriter.js'; |
29 | import theLinkBuilder from './LinkBuilder.js'; |
30 | import NavHtmlBuilder from './NavHtmlBuilder.js'; |
31 | import { marked } from 'marked'; |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | class ClassHtmlBuilder { |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | #classesCounter; |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | #className; |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | #html; |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | #rootPath; |
68 | |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | #methodsOrPropertiesDoc; |
75 | |
76 | |
77 | |
78 | |
79 | |
80 | constructor ( ) { |
81 | Object.freeze ( this ); |
82 | this.#classesCounter = 0; |
83 | } |
84 | |
85 | |
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 | #buildParamsTable ( methodOrPropertyDoc ) { |
93 | this.#html += '<h4>Parameters</h4>'; |
94 | this.#html += '<table class="params"><tr><th>Name</th> <th>Type</th> <th>Description</th></tr>'; |
95 | methodOrPropertyDoc.params.forEach ( |
96 | param => { |
97 | const paramDoc = methodOrPropertyDoc?.commentsDoc?.params?.find ( first => first.name === param ); |
98 | const paramType = paramDoc?.type ? theLinkBuilder.getTypeLinks ( paramDoc.type, this.#rootPath ) : '???'; |
99 | const paramDesc = |
100 | paramDoc?.desc |
101 | ? |
102 | theLinkBuilder.getDescLink ( paramDoc.desc, this.#rootPath ) |
103 | : |
104 | ' ...No description provided. Coming soon?'; |
105 | this.#html += `<tr><td>${param}</td> <td>${paramType}</td> <td>${paramDesc}</td></tr>`; |
106 | } |
107 | ); |
108 | this.#html += '</table>'; |
109 | } |
110 | |
111 | |
112 | |
113 | |
114 | |
115 | |
116 | |
117 | |
118 | #buildParamsHeader ( methodOrPropertyDoc ) { |
119 | let params = ''; |
120 | if ( methodOrPropertyDoc.params ) { |
121 | methodOrPropertyDoc.params.forEach ( |
122 | param => params += param + ', ' |
123 | ); |
124 | params = params.substring ( 0, params.length - 2 ); |
125 | } |
126 | |
127 | return ` ( ${params} )`; |
128 | } |
129 | |
130 | |
131 | |
132 | |
133 | |
134 | |
135 | #buildMethodOrPropertyHeader ( methodOrPropertyDoc ) { |
136 | |
137 | |
138 | const cssClassName = methodOrPropertyDoc.private ? 'private' : 'public'; |
139 | |
140 | |
141 | |
142 | const readOnlyPrefix = |
143 | 'get' === |
144 | methodOrPropertyDoc.kind |
145 | && |
146 | ! this.#methodsOrPropertiesDoc.find ( |
147 | method => 'set' === method.kind && methodOrPropertyDoc.name === method.name |
148 | ) |
149 | ? |
150 | '<span>readonly </span>' |
151 | : |
152 | ''; |
153 | |
154 | |
155 | const getSetPrefix = |
156 | 'set' === methodOrPropertyDoc.kind || 'get' === methodOrPropertyDoc.kind |
157 | ? |
158 | '<span>' + methodOrPropertyDoc.kind + '</span> ' |
159 | : |
160 | ''; |
161 | |
162 | |
163 | const asyncPrefix = methodOrPropertyDoc.async ? '<span>async</span> ' : ''; |
164 | |
165 | |
166 | const staticPrefix = methodOrPropertyDoc.static ? '<span>static</span> ' : ''; |
167 | |
168 | |
169 | const namePrefix = methodOrPropertyDoc.private ? '#' : ''; |
170 | |
171 | |
172 | const methodName = |
173 | 'constructor' === methodOrPropertyDoc.name ? `<span>new</span> ${this.#className}` : methodOrPropertyDoc.name; |
174 | |
175 | |
176 | const paramsPostfix = |
177 | 'method' === methodOrPropertyDoc.isA && 0 === getSetPrefix.length |
178 | ? |
179 | this.#buildParamsHeader ( methodOrPropertyDoc ) |
180 | : |
181 | ''; |
182 | |
183 | |
184 | const typePostfix = |
185 | methodOrPropertyDoc?.commentsDoc?.type |
186 | ? |
187 | ' <span> : ' + |
188 | theLinkBuilder.getTypeLinks ( methodOrPropertyDoc.commentsDoc.type, this.#rootPath ) + |
189 | '</span>' |
190 | : |
191 | ''; |
192 | |
193 | |
194 | this.#html += `<div class="${cssClassName}">`; |
195 | this.#html += |
196 | `<h3>${readOnlyPrefix}${asyncPrefix}${staticPrefix}${getSetPrefix}${namePrefix}` + |
197 | `${methodName}${paramsPostfix}${typePostfix}</h3>`; |
198 | } |
199 | |
200 | |
201 | |
202 | |
203 | |
204 | |
205 | #buildMethodOrProperty ( methodOrPropertyDoc ) { |
206 | |
207 | |
208 | this.#buildMethodOrPropertyHeader ( methodOrPropertyDoc ); |
209 | |
210 | |
211 | let desc = |
212 | methodOrPropertyDoc?.commentsDoc?.desc |
213 | ? |
214 | theLinkBuilder.getDescLink ( methodOrPropertyDoc.commentsDoc.desc, this.#rootPath ) |
215 | : |
216 | ' ...No description provided. Coming soon?'; |
217 | |
218 | if ( 'set' === methodOrPropertyDoc.kind ) { |
219 | const getter = this.#methodsOrPropertiesDoc.find ( |
220 | method => 'get' === method.kind && methodOrPropertyDoc.name === method.name |
221 | ); |
222 | desc = getter && getter?.commentsDoc?.desc ? '' : desc; |
223 | } |
224 | |
225 | this.#html += `<div>${desc}</div>`; |
226 | |
227 | |
228 | if ( methodOrPropertyDoc?.commentsDoc?.sample ) { |
229 | this.#html += `<div>${marked.parse ( methodOrPropertyDoc?.commentsDoc?.sample )}</div>`; |
230 | } |
231 | |
232 | |
233 | const sourceLink = theLinkBuilder.getSourceLink ( methodOrPropertyDoc ); |
234 | this.#html += |
235 | `<div>Source : <a href="${sourceLink}"> file ${methodOrPropertyDoc.file}` + |
236 | ` at line ${methodOrPropertyDoc.line}</a></div>`; |
237 | |
238 | |
239 | if ( |
240 | methodOrPropertyDoc.params |
241 | && |
242 | 0 !== methodOrPropertyDoc.params.length |
243 | && |
244 | 'set' !== methodOrPropertyDoc.kind |
245 | && |
246 | 'get' !== methodOrPropertyDoc.kind |
247 | ) { |
248 | this.#buildParamsTable ( methodOrPropertyDoc ); |
249 | } |
250 | |
251 | |
252 | if ( |
253 | methodOrPropertyDoc?.commentsDoc?.returns |
254 | && |
255 | 'method' === methodOrPropertyDoc.isA |
256 | && |
257 | 'set' !== methodOrPropertyDoc.kind |
258 | && |
259 | 'get' !== methodOrPropertyDoc.kind |
260 | && |
261 | 'constructor' !== methodOrPropertyDoc.kind |
262 | ) { |
263 | const returnType = |
264 | methodOrPropertyDoc.commentsDoc?.returns?.type |
265 | ? |
266 | theLinkBuilder.getTypeLinks ( methodOrPropertyDoc.commentsDoc.returns.type, this.#rootPath ) |
267 | : |
268 | '???'; |
269 | |
270 | const returnDesc = methodOrPropertyDoc?.commentsDoc?.returns?.desc ?? ' ...No description provided. Coming soon?'; |
271 | |
272 | this.#html += `<h4>Returns</h4><div>${returnDesc}</div>` + |
273 | `<div>Type : ${returnType}</div>`; |
274 | } |
275 | |
276 | this.#html += '</div>'; |
277 | } |
278 | |
279 | |
280 | |
281 | |
282 | |
283 | |
284 | #buildMethodsAndProperties ( heading ) { |
285 | |
286 | |
287 | if ( 0 === this.#methodsOrPropertiesDoc.length ) { |
288 | return; |
289 | } |
290 | |
291 | |
292 | this.#html += `${heading}`; |
293 | |
294 | |
295 | this.#methodsOrPropertiesDoc.forEach ( |
296 | methodOrPropertyDoc => this.#buildMethodOrProperty ( methodOrPropertyDoc ) |
297 | ); |
298 | } |
299 | |
300 | |
301 | |
302 | |
303 | |
304 | |
305 | get classesCounter ( ) { return this.#classesCounter; } |
306 | |
307 | |
308 | |
309 | |
310 | |
311 | |
312 | build ( classDoc ) { |
313 | |
314 | this.#classesCounter ++; |
315 | |
316 | |
317 | this.#rootPath = classDoc.rootPath; |
318 | |
319 | |
320 | this.#className = classDoc.name; |
321 | |
322 | |
323 | this.#html = |
324 | '<!DOCTYPE html><html><head><meta charset="UTF-8">' + |
325 | `<link type="text/css" rel="stylesheet" href="${classDoc.rootPath}ESSimpleDoc.css"></head>` + |
326 | '<body class=\'havePrivateButton\'>'; |
327 | |
328 | |
329 | const navHtmlBuilder = new NavHtmlBuilder ( ); |
330 | this.#html += navHtmlBuilder.build ( this.#rootPath ); |
331 | |
332 | |
333 | const superClass = |
334 | classDoc?.superClass |
335 | ? |
336 | '<span> extends ' + theLinkBuilder.getClassLink ( classDoc.superClass, this.#rootPath ) + '</span>' |
337 | : |
338 | ''; |
339 | |
340 | this.#html += `<h1><span>Class</span> ${classDoc.name} ${superClass}</h1>`; |
341 | |
342 | |
343 | const desc = |
344 | classDoc?.commentsDoc?.desc |
345 | ? |
346 | theLinkBuilder.getDescLink ( classDoc.commentsDoc.desc, this.#rootPath ) |
347 | : |
348 | ' ...No description provided. Coming soon?'; |
349 | |
350 | this.#html += `<div>${desc}</div>`; |
351 | |
352 | |
353 | if ( classDoc?.commentsDoc?.sample ) { |
354 | this.#html += `<div>${marked.parse ( classDoc?.commentsDoc?.sample )}</div>`; |
355 | } |
356 | |
357 | |
358 | const sourceLink = theLinkBuilder.getSourceLink ( classDoc ); |
359 | this.#html += `<div>Source : <a href="${sourceLink}"> file ${classDoc.file} at line ${classDoc.line}</a></div>`; |
360 | |
361 | |
362 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
363 | methodOrProperty => ( |
364 | 'method' === methodOrProperty.isA && |
365 | 'constructor' === methodOrProperty.kind |
366 | ) |
367 | ); |
368 | this.#buildMethodsAndProperties ( '<h2 class="public">Constructor</h2>' ); |
369 | |
370 | |
371 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
372 | methodOrProperty => ( |
373 | 'property' === methodOrProperty.isA && |
374 | ! methodOrProperty.private |
375 | ) |
376 | ).sort ( ( first, second ) => first.name.localeCompare ( second.name ) ); |
377 | this.#buildMethodsAndProperties ( '<h2 class="public">Public properties</h2>' ); |
378 | |
379 | |
380 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
381 | methodOrProperty => ( |
382 | 'method' === methodOrProperty.isA && |
383 | ! methodOrProperty.private && |
384 | ( 'set' === methodOrProperty.kind || 'get' === methodOrProperty.kind ) && |
385 | 'constructor' !== methodOrProperty.kind |
386 | ) |
387 | ).sort ( ( first, second ) => ( first.name + first.kind ).localeCompare ( second.name + second.kind ) ); |
388 | this.#buildMethodsAndProperties ( '<h2 class="public">Public getters and setters</h2>' ); |
389 | |
390 | |
391 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
392 | methodOrProperty => ( |
393 | 'method' === methodOrProperty.isA && |
394 | ! methodOrProperty.private && |
395 | 'set' !== methodOrProperty.kind && |
396 | 'get' !== methodOrProperty.kind && |
397 | 'constructor' !== methodOrProperty.kind |
398 | ) |
399 | ).sort ( ( first, second ) => first.name.localeCompare ( second.name ) ); |
400 | this.#buildMethodsAndProperties ( '<h2 class="public">Public methods</h2>' ); |
401 | |
402 | |
403 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
404 | methodOrProperty => ( |
405 | 'property' === methodOrProperty.isA && |
406 | methodOrProperty.private |
407 | ) |
408 | ).sort ( ( first, second ) => first.name.localeCompare ( second.name ) ); |
409 | this.#buildMethodsAndProperties ( '<h2 class="private">Private properties</h2>' ); |
410 | |
411 | |
412 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
413 | methodOrProperty => ( |
414 | 'method' === methodOrProperty.isA && |
415 | methodOrProperty.private && |
416 | ( 'set' === methodOrProperty.kind || 'get' === methodOrProperty.kind ) && |
417 | 'constructor' !== methodOrProperty.kind |
418 | ) |
419 | ).sort ( ( first, second ) => ( first.name + first.kind ).localeCompare ( second.name + second.kind ) ); |
420 | this.#buildMethodsAndProperties ( '<h2 class="private">Private getters and setters</h2>' ); |
421 | |
422 | |
423 | this.#methodsOrPropertiesDoc = classDoc.methodsAndProperties.filter ( |
424 | methodOrProperty => ( |
425 | 'method' === methodOrProperty.isA && |
426 | methodOrProperty.private && |
427 | 'set' !== methodOrProperty.kind && |
428 | 'get' !== methodOrProperty.kind && |
429 | 'constructor' !== methodOrProperty.kind |
430 | ) |
431 | ).sort ( ( first, second ) => first.name.localeCompare ( second.name ) ); |
432 | this.#buildMethodsAndProperties ( '<h2 class="private">Private methods</h2>' ); |
433 | |
434 | |
435 | this.#html += navHtmlBuilder.footer; |
436 | this.#html += |
437 | '<script>' + |
438 | 'document.getElementById(\'showPrivateNav\')' + |
439 | '.addEventListener(\'click\',()=>document.body.classList.toggle(\'showPrivate\'))' + |
440 | '</script>'; |
441 | this.#html += '</body></html>'; |
442 | |
443 | |
444 | const dirs = classDoc.file.split ( '/' ); |
445 | dirs.pop ( ); |
446 | new FileWriter ( ).write ( dirs, classDoc.name + '.html', this.#html ); |
447 | } |
448 | } |
449 | |
450 | export default ClassHtmlBuilder; |
451 | |
452 | |
453 | |