From 9e5429b9ab01efd825eb064c2b06733dd7c4a675 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Wed, 4 Dec 2024 10:42:59 -0500 Subject: [PATCH] dev: improve module docgen - fixes some bugs - adds list of relative requires --- package-lock.json | 1 + src/backend/src/modules/web/README.md | 25 ++- .../src/modules/web/WebServerService.js | 4 - tools/module-docgen/defs.js | 161 ++++++++++++++++++ tools/module-docgen/main.js | 119 +++---------- tools/module-docgen/package.json | 1 + 6 files changed, 207 insertions(+), 104 deletions(-) create mode 100644 tools/module-docgen/defs.js diff --git a/package-lock.json b/package-lock.json index b5a152dd..a34e4087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17661,6 +17661,7 @@ "dependencies": { "@babel/parser": "^7.26.2", "@babel/traverse": "^7.25.9", + "dedent": "^1.5.3", "doctrine": "^3.0.0" } }, diff --git a/src/backend/src/modules/web/README.md b/src/backend/src/modules/web/README.md index 22bdb218..726e41e9 100644 --- a/src/backend/src/modules/web/README.md +++ b/src/backend/src/modules/web/README.md @@ -20,13 +20,7 @@ Initializes socket.io ###### Parameters -- `server`: The server to attach socket.io to. - -### WebModule - -undefined - -#### Listeners +- **server:** The server to attach socket.io to. ### WebServerService @@ -53,7 +47,22 @@ If the `config.http_port` is set to 'auto', it will try to find an available por Once the server is up and running, it emits the 'start.webserver' and 'ready.webserver' events. If the `config.env` is set to 'dev' and `config.no_browser_launch` is false, it will open the Puter URL in the default browser. -##### `start.webserver` +## Notes +### Outside Imports +This module has external relative imports. When these are +removed it may become possible to move this module to an +extension. +**Imports:** +- `../../services/BaseService` (use.BaseService) +- `../../api/eggspress.js` +- `../../util/context.js` +- `../../services/BaseService.js` +- `../../config.js` +- `../../middleware/auth.js` +- `../../util/strutil.js` +- `../../fun/dev-console-ui-utils.js` +- `../../helpers.js` +- `../../fun/logos.js` diff --git a/src/backend/src/modules/web/WebServerService.js b/src/backend/src/modules/web/WebServerService.js index de1daaf8..bbd393c9 100644 --- a/src/backend/src/modules/web/WebServerService.js +++ b/src/backend/src/modules/web/WebServerService.js @@ -94,10 +94,6 @@ class WebServerService extends BaseService { * * @return {Promise} A promise that resolves when the server is up and running. */ - // eslint-disable-next-line no-unused-vars - async ['__on_start.webserver'] () { - // ... rest of the method code - } async ['__on_start.webserver'] () { await es_import_promise; diff --git a/tools/module-docgen/defs.js b/tools/module-docgen/defs.js new file mode 100644 index 00000000..88958955 --- /dev/null +++ b/tools/module-docgen/defs.js @@ -0,0 +1,161 @@ +const dedent = require('dedent'); +const doctrine = require('doctrine'); + +class Out { + constructor () { + this.str = ''; + const fn = this.out.bind(this); + fn.h = this.h.bind(this); + fn.lf = this.lf.bind(this); + fn.text = () => this.str; + return fn; + } + + h (n, text) { + this.str += '#'.repeat(n) + ' ' + text + '\n\n'; + } + + lf () { this.str += '\n'; } + + out (str) { + this.str += str; + } +} + +class Doc { + constructor () { + this._construct(); + } + provide_comment (comment) { + const parsed_comment = doctrine.parse(comment.value, { unwrap: true }); + this.comment = parsed_comment.description; + } +} + +class ModuleDoc extends Doc { + _construct () { + this.services = []; + this.requires = []; + } + + add_service () { + const service = new ServiceDoc(); + this.services.push(service); + return service; + } + + ready () { + this.notes = []; + const rel_requires = this.requires.filter(r => r.startsWith('../')); + if ( rel_requires.length > 0 ) { + this.notes.push({ + title: 'Outside Imports', + desc: dedent(` + This module has external relative imports. When these are + removed it may become possible to move this module to an + extension. + + **Imports:** + ${rel_requires.map(r => { + let maybe_aside = ''; + if ( r.endsWith('BaseService') ) { + maybe_aside = ' (use.BaseService)'; + } + return `- \`${r}\`` + maybe_aside; + }).join('\n')} + `) + }); + } + } + + toMarkdown ({ hl, out } = { hl: 1 }) { + this.ready(); + + out = out ?? new Out(); + + out.h(hl, this.name); + + out(this.comment + '\n\n'); + + if ( this.services.length > 0 ) { + out.h(hl + 1, 'Services'); + + for ( const service of this.services ) { + service.toMarkdown({ out, hl: hl + 2 }); + } + } + + if ( this.notes.length > 0 ) { + out.h(hl + 1, 'Notes'); + for ( const note of this.notes ) { + out.h(hl + 2, note.title); + out(note.desc); + out.lf(); + } + } + + + return out.text(); + } +} + +class ServiceDoc extends Doc { + _construct () { + this.listeners = []; + } + + provide_comment (comment) { + const parsed_comment = doctrine.parse(comment.value, { unwrap: true }); + this.comment = parsed_comment.description; + } + + provide_listener (listener) { + const parsed_comment = doctrine.parse(listener.comment, { unwrap: true }); + + const params = []; + for ( const tag of parsed_comment.tags ) { + if ( tag.title !== 'evtparam' ) continue; + const name = tag.description.slice(0, tag.description.indexOf(' ')); + const desc = tag.description.slice(tag.description.indexOf(' ')); + params.push({ name, desc }) + } + + this.listeners.push({ + ...listener, + comment: parsed_comment.description, + params, + }); + } + + toMarkdown ({ hl, out } = { hl: 1 }) { + out = out ?? new Out(); + + out.h(hl, this.name); + + out(this.comment + '\n\n'); + + if ( this.listeners.length > 0 ) { + out.h(hl + 1, 'Listeners'); + + for ( const listener of this.listeners ) { + out.h(hl + 2, '`' + listener.key + '`'); + out (listener.comment + '\n\n'); + + if ( listener.params.length > 0 ) { + out.h(hl + 3, 'Parameters'); + for ( const param of listener.params ) { + out(`- **${param.name}:** ${param.desc}\n`); + } + out.lf(); + } + } + } + + return out.text(); + } +} + +module.exports = { + ModuleDoc, + ServiceDoc, +}; diff --git a/tools/module-docgen/main.js b/tools/module-docgen/main.js index 34f4479f..4c170482 100644 --- a/tools/module-docgen/main.js +++ b/tools/module-docgen/main.js @@ -5,51 +5,9 @@ const rootdir = path_.resolve(process.argv[2] ?? '.'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; -const doctrine = require('doctrine'); +const { ModuleDoc } = require("./defs"); -const def_module = { - services: [], -}; - -const to_module_add_service = (def_module, values) => { - def_module.services.push({ - ...values, - }); -} - -const to_service_add_listener = (def_service, values) => { - const parsed_comment = doctrine.parse(values.comment, { unwrap: true }); - - const params = []; - for ( const tag of parsed_comment.tags ) { - if ( tag.title !== 'evtparam' ) continue; - const name = tag.description.slice(0, tag.description.indexOf(' ')); - const desc = tag.description.slice(tag.description.indexOf(' ')); - params.push({ name, desc }) - } - - def_service.listeners.push({ - ...values, - comment: parsed_comment.description, - params, - }); -}; - -const to_service_add_comment = (def_service, comment) => { - console.log('comment', comment); - const parsed_comment = doctrine.parse(comment.value, { unwrap: true }); - def_service.comment = parsed_comment.description; -}; - -const to_module_add_comment = (def_module, comment) => { - console.log('comment', comment); - const parsed_comment = doctrine.parse(comment.value, { unwrap: true }); - def_module.comment = parsed_comment.description; -}; - -const create_service = () => ({ - listeners: [] -}); +const doc_module = new ModuleDoc(); // List files in this directory const files = fs.readdirSync(rootdir); @@ -67,16 +25,21 @@ for ( const file of files ) { if ( type === null ) continue; - const def_service = create_service(); - console.log('file', file); const code = fs.readFileSync(path_.join(rootdir, file), 'utf8'); const ast = parser.parse(code); traverse(ast, { + CallExpression (path) { + const callee = path.get('callee'); + if ( ! callee.isIdentifier() ) return; + + if ( callee.node.name === 'require' ) { + doc_module.requires.push(path.node.arguments[0].value); + } + }, ClassDeclaration (path) { const node = path.node; const name = node.id.name; - def_service.name = name; // Skip utility classes (for now) if ( name !== file.slice(0, -3) ) { @@ -88,16 +51,24 @@ for ( const file of files ) { node.leadingComments[node.leadingComments.length - 1] )) ?? ''; - if ( type === 'module' ) { - def_module.name = name; - if ( comment !== '' ) { - to_module_add_comment(def_module, comment); - } - return; + let doc_item = doc_module; + if ( type !== 'module' ) { + doc_item = doc_module.add_service(); } + doc_item.name = name; if ( comment !== '' ) { - to_service_add_comment(def_service, comment); + doc_item.provide_comment(comment); + } + + if ( type === 'module' ) { + return; + } + + + if ( comment !== '' ) { + doc_item.provide_comment(comment); + // to_service_add_comment(def_service, comment); } console.log('class', name); @@ -111,7 +82,7 @@ for ( const file of files ) { // we want the list of keys in the object: const params = member.params?.[1]?.properties ?? []; - to_service_add_listener(def_service, { + doc_item.provide_listener({ key: key.slice(5), comment, params, @@ -119,48 +90,12 @@ for ( const file of files ) { } console.log(member.type, key, member.leadingComments); }); - // module_info.services.push({ - // name, - // file, - // }); } }) - - to_module_add_service(def_module, def_service); - // console.log('parsed?', parsed); } -console.log('module', JSON.stringify(def_module, undefined, ' ')); - const outfile = path_.join(rootdir, 'README.md'); -let out = ''; - -out += `# ${def_module.name}\n\n`; - -if ( def_module.comment ) { - out += `${def_module.comment}\n\n`; -} - -out += '## Services\n\n'; - -for ( const service of def_module.services ) { - out += `### ${service.name}\n\n`; - out += `${service.comment}\n\n`; - - out += '#### Listeners\n\n'; - for ( const listener of service.listeners ) { - out += `##### \`${listener.key}\`\n\n`; - out += `${listener.comment}\n\n`; - - if ( listener.params.length > 0 ) { - out += '###### Parameters\n\n'; - for ( const param of listener.params ) { - out += `- \`${param.name}\`: ${param.desc}\n`; - } - out += '\n'; - } - } -} +const out = doc_module.toMarkdown(); fs.writeFileSync(outfile, out); \ No newline at end of file diff --git a/tools/module-docgen/package.json b/tools/module-docgen/package.json index cf258dc8..4aa2bdd5 100644 --- a/tools/module-docgen/package.json +++ b/tools/module-docgen/package.json @@ -12,6 +12,7 @@ "dependencies": { "@babel/parser": "^7.26.2", "@babel/traverse": "^7.25.9", + "dedent": "^1.5.3", "doctrine": "^3.0.0" } }