From 5c3a65060d7ed28be7b6e8e37a635e7b07b02789 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Wed, 4 Dec 2024 14:39:40 -0500 Subject: [PATCH] dev: include library functions in module docgen --- tools/module-docgen/defs.js | 68 +++++++++++++++++++- tools/module-docgen/main.js | 102 +++++++++++++++++++++--------- tools/module-docgen/processors.js | 50 +++++++++++++++ 3 files changed, 188 insertions(+), 32 deletions(-) diff --git a/tools/module-docgen/defs.js b/tools/module-docgen/defs.js index 11cd43ff..d99ca827 100644 --- a/tools/module-docgen/defs.js +++ b/tools/module-docgen/defs.js @@ -36,6 +36,7 @@ class ModuleDoc extends Doc { _construct () { this.services = []; this.requires = []; + this.libs = []; } add_service () { @@ -44,6 +45,12 @@ class ModuleDoc extends Doc { return service; } + add_lib () { + const lib = new LibDoc(); + this.libs.push(lib); + return lib; + } + ready () { this.notes = []; const rel_requires = this.requires.filter(r => r.startsWith('../')); @@ -85,6 +92,14 @@ class ModuleDoc extends Doc { } } + if ( this.libs.length > 0 ) { + out.h(hl + 1, 'Libraries'); + + for ( const lib of this.libs ) { + lib.toMarkdown({ out, hl: hl + 2 }); + } + } + if ( this.notes.length > 0 ) { out.h(hl + 1, 'Notes'); for ( const note of this.notes ) { @@ -93,7 +108,7 @@ class ModuleDoc extends Doc { out.lf(); } } - + return out.text(); } @@ -191,6 +206,57 @@ class ServiceDoc extends Doc { } } +class LibDoc extends Doc { + _construct () { + this.functions = []; + } + + provide_function ({ key, comment, params }) { + const parsed_comment = doctrine.parse(comment, { unwrap: true }); + + const parsed_params = []; + for ( const tag of parsed_comment.tags ) { + if ( tag.title !== 'param' ) continue; + const name = tag.name; + const desc = tag.description; + parsed_params.push({ name, desc }); + } + + this.functions.push({ + key, + comment: parsed_comment.description, + params: parsed_params, + }); + } + + toMarkdown ({ hl, out } = { hl: 1 }) { + out = out ?? new Out(); + + out.h(hl, this.name); + + console.log('functions?', this.functions); + + if ( this.functions.length > 0 ) { + out.h(hl + 1, 'Functions'); + + for ( const func of this.functions ) { + out.h(hl + 2, '`' + func.key + '`'); + out(func.comment + '\n\n'); + + if ( func.params.length > 0 ) { + out.h(hl + 3, 'Parameters'); + for ( const param of func.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 12089a98..a23e78a2 100644 --- a/tools/module-docgen/main.js +++ b/tools/module-docgen/main.js @@ -10,40 +10,10 @@ const processors = require("./processors"); const doc_module = new ModuleDoc(); -// List files in this directory -const files = fs.readdirSync(rootdir); -for ( const file of files ) { - const stat = fs.statSync(path_.join(rootdir, file)); - if ( stat.isDirectory() ) { - continue; - } - if ( ! file.endsWith('.js') ) continue; - - const type = - file.endsWith('Service.js') ? 'service' : - file.endsWith('Module.js') ? 'module' : - null; - - if ( type === null ) continue; - - console.log('file', file); - const code = fs.readFileSync(path_.join(rootdir, file), 'utf8'); - - const firstLine = code.slice(0, code.indexOf('\n')); - let metadata = {}; - const METADATA_PREFIX = '// METADATA // '; - if ( firstLine.startsWith(METADATA_PREFIX) ) { - metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length)); - } - +const handle_file = (code, context) => { const ast = parser.parse(code); const traverse_callbacks = {}; - const context = { - type, - doc_module, - filename: file, - }; for ( const processor of processors ) { if ( processor.match(context) ) { for ( const key in processor.traverse ) { @@ -57,14 +27,84 @@ for ( const file of files ) { for ( const key in traverse_callbacks ) { traverse(ast, { [key] (path) { + context.skip = false; for ( const callback of traverse_callbacks[key] ) { callback(path, context); + if ( context.skip ) return; } } }); } } +// Module and class files +{ + const files = fs.readdirSync(rootdir); + for ( const file of files ) { + const stat = fs.statSync(path_.join(rootdir, file)); + if ( stat.isDirectory() ) { + continue; + } + if ( ! file.endsWith('.js') ) continue; + + const type = + file.endsWith('Service.js') ? 'service' : + file.endsWith('Module.js') ? 'module' : + null; + + if ( type === null ) continue; + + console.log('file', file); + const code = fs.readFileSync(path_.join(rootdir, file), 'utf8'); + + const firstLine = code.slice(0, code.indexOf('\n')); + let metadata = {}; + const METADATA_PREFIX = '// METADATA // '; + if ( firstLine.startsWith(METADATA_PREFIX) ) { + metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length)); + } + + const context = { + metadata, + type, + doc_module, + filename: file, + }; + + handle_file(code, context); + } +} + +// Library files +{ + const files = fs.readdirSync(path_.join(rootdir, 'lib')); + for ( const file of files ) { + if ( file.startsWith('_') ) continue; + + const code = fs.readFileSync(path_.join(rootdir, 'lib', file), 'utf8'); + + const firstLine = code.slice(0, code.indexOf('\n')); + let metadata = {}; + const METADATA_PREFIX = '// METADATA // '; + if ( firstLine.startsWith(METADATA_PREFIX) ) { + metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length)); + } + + const doc_item = doc_module.add_lib(); + doc_item.name = metadata.def ?? file.slice(0, -3); + + const context = { + metadata, + type: 'lib', + doc_module, + doc_item, + filename: file, + }; + + handle_file(code, context); + } +} + const outfile = path_.join(rootdir, 'README.md'); const out = doc_module.toMarkdown(); diff --git a/tools/module-docgen/processors.js b/tools/module-docgen/processors.js index 8063b940..3c51cc09 100644 --- a/tools/module-docgen/processors.js +++ b/tools/module-docgen/processors.js @@ -39,9 +39,15 @@ processors.push({ ClassDeclaration (path, context) { context.doc_item = context.doc_module; if ( context.type === 'service' ) { + // Skip if class name doesn't end with 'Service' + if ( ! path.node.id.name.endsWith('Service') ) { + context.skip = true; + return; + } context.doc_item = context.doc_module.add_service(); } context.doc_item.name = path.node.id.name; + if ( context.comment === '' ) return; context.doc_item.provide_comment(context.comment); } } @@ -91,4 +97,48 @@ processors.push({ } }); +processors.push({ + title: 'provide library function documentation', + match (context) { + return context.type === 'lib'; + }, + traverse: { + VariableDeclaration (path, context) { + // skip non-const declarations + if ( path.node.kind !== 'const' ) return; + + // skip declarations with multiple declarators + if ( path.node.declarations.length !== 1 ) return; + + // skip declarations without an initializer + if ( ! path.node.declarations[0].init ) return; + + // skip declarations that aren't in the root scope + if ( path.scope.parent ) return; + + console.log('path.node', path.node.declarations); + + // is it a function? + if ( ! ['FunctionExpression', 'ArrowFunctionExpression'].includes( + path.node.declarations[0].init.type + ) ) return; + + // get the name of the function + const name = path.node.declarations[0].id.name; + + // get the comment + const comment = path.node.leadingComments?.[0]?.value ?? ''; + + // get the parameters + const params = path.node.declarations[0].init.params ?? []; + + context.doc_item.provide_function({ + key: name, + comment, + params, + }); + } + } +}); + module.exports = processors;