dev: include library functions in module docgen

This commit is contained in:
KernelDeimos 2024-12-04 14:39:40 -05:00
parent 5fa0a0263d
commit 5c3a65060d
3 changed files with 188 additions and 32 deletions

View File

@ -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 ) {
@ -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,

View File

@ -10,9 +10,37 @@ 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 handle_file = (code, context) => {
const ast = parser.parse(code);
const traverse_callbacks = {};
for ( const processor of processors ) {
if ( processor.match(context) ) {
for ( const key in processor.traverse ) {
if ( ! traverse_callbacks[key] ) {
traverse_callbacks[key] = [];
}
traverse_callbacks[key].push(processor.traverse[key]);
}
}
}
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;
@ -36,32 +64,44 @@ for ( const file of files ) {
metadata = JSON.parse(firstLine.slice(METADATA_PREFIX.length));
}
const ast = parser.parse(code);
const traverse_callbacks = {};
const context = {
metadata,
type,
doc_module,
filename: file,
};
for ( const processor of processors ) {
if ( processor.match(context) ) {
for ( const key in processor.traverse ) {
if ( ! traverse_callbacks[key] ) {
traverse_callbacks[key] = [];
handle_file(code, context);
}
traverse_callbacks[key].push(processor.traverse[key]);
}
// 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));
}
}
}
for ( const key in traverse_callbacks ) {
traverse(ast, {
[key] (path) {
for ( const callback of traverse_callbacks[key] ) {
callback(path, context);
}
}
});
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);
}
}

View File

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