dev: improve module docgen

- fixes some bugs
- adds list of relative requires
This commit is contained in:
KernelDeimos 2024-12-04 10:42:59 -05:00
parent c0257c482f
commit 9e5429b9ab
6 changed files with 207 additions and 104 deletions

1
package-lock.json generated
View File

@ -17661,6 +17661,7 @@
"dependencies": { "dependencies": {
"@babel/parser": "^7.26.2", "@babel/parser": "^7.26.2",
"@babel/traverse": "^7.25.9", "@babel/traverse": "^7.25.9",
"dedent": "^1.5.3",
"doctrine": "^3.0.0" "doctrine": "^3.0.0"
} }
}, },

View File

@ -20,13 +20,7 @@ Initializes socket.io
###### Parameters ###### Parameters
- `server`: The server to attach socket.io to. - **server:** The server to attach socket.io to.
### WebModule
undefined
#### Listeners
### WebServerService ### 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. 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. 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`

View File

@ -94,10 +94,6 @@ class WebServerService extends BaseService {
* *
* @return {Promise} A promise that resolves when the server is up and running. * @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'] () { async ['__on_start.webserver'] () {
await es_import_promise; await es_import_promise;

161
tools/module-docgen/defs.js Normal file
View File

@ -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,
};

View File

@ -5,51 +5,9 @@ const rootdir = path_.resolve(process.argv[2] ?? '.');
const parser = require('@babel/parser'); const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default; const traverse = require('@babel/traverse').default;
const doctrine = require('doctrine'); const { ModuleDoc } = require("./defs");
const def_module = { const doc_module = new ModuleDoc();
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: []
});
// List files in this directory // List files in this directory
const files = fs.readdirSync(rootdir); const files = fs.readdirSync(rootdir);
@ -67,16 +25,21 @@ for ( const file of files ) {
if ( type === null ) continue; if ( type === null ) continue;
const def_service = create_service();
console.log('file', file); console.log('file', file);
const code = fs.readFileSync(path_.join(rootdir, file), 'utf8'); const code = fs.readFileSync(path_.join(rootdir, file), 'utf8');
const ast = parser.parse(code); const ast = parser.parse(code);
traverse(ast, { 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) { ClassDeclaration (path) {
const node = path.node; const node = path.node;
const name = node.id.name; const name = node.id.name;
def_service.name = name;
// Skip utility classes (for now) // Skip utility classes (for now)
if ( name !== file.slice(0, -3) ) { if ( name !== file.slice(0, -3) ) {
@ -88,16 +51,24 @@ for ( const file of files ) {
node.leadingComments[node.leadingComments.length - 1] node.leadingComments[node.leadingComments.length - 1]
)) ?? ''; )) ?? '';
if ( type === 'module' ) { let doc_item = doc_module;
def_module.name = name; if ( type !== 'module' ) {
if ( comment !== '' ) { doc_item = doc_module.add_service();
to_module_add_comment(def_module, comment);
}
return;
} }
doc_item.name = name;
if ( comment !== '' ) { 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); console.log('class', name);
@ -111,7 +82,7 @@ for ( const file of files ) {
// we want the list of keys in the object: // we want the list of keys in the object:
const params = member.params?.[1]?.properties ?? []; const params = member.params?.[1]?.properties ?? [];
to_service_add_listener(def_service, { doc_item.provide_listener({
key: key.slice(5), key: key.slice(5),
comment, comment,
params, params,
@ -119,48 +90,12 @@ for ( const file of files ) {
} }
console.log(member.type, key, member.leadingComments); 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'); const outfile = path_.join(rootdir, 'README.md');
let out = ''; const out = doc_module.toMarkdown();
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';
}
}
}
fs.writeFileSync(outfile, out); fs.writeFileSync(outfile, out);

View File

@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@babel/parser": "^7.26.2", "@babel/parser": "^7.26.2",
"@babel/traverse": "^7.25.9", "@babel/traverse": "^7.25.9",
"dedent": "^1.5.3",
"doctrine": "^3.0.0" "doctrine": "^3.0.0"
} }
} }