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": {
"@babel/parser": "^7.26.2",
"@babel/traverse": "^7.25.9",
"dedent": "^1.5.3",
"doctrine": "^3.0.0"
}
},

View File

@ -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`

View File

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

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 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);

View File

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