diff --git a/tools/module-docgen/defs.js b/tools/module-docgen/defs.js index 889589557..11cd43ffd 100644 --- a/tools/module-docgen/defs.js +++ b/tools/module-docgen/defs.js @@ -102,6 +102,7 @@ class ModuleDoc extends Doc { class ServiceDoc extends Doc { _construct () { this.listeners = []; + this.methods = []; } provide_comment (comment) { @@ -127,6 +128,24 @@ class ServiceDoc extends Doc { }); } + provide_method (method) { + const parsed_comment = doctrine.parse(method.comment, { unwrap: true }); + + const params = []; + for ( const tag of parsed_comment.tags ) { + if ( tag.title !== 'param' ) continue; + const name = tag.name; + const desc = tag.description; + params.push({ name, desc }) + } + + this.methods.push({ + ...method, + comment: parsed_comment.description, + params, + }); + } + toMarkdown ({ hl, out } = { hl: 1 }) { out = out ?? new Out(); @@ -151,6 +170,23 @@ class ServiceDoc extends Doc { } } + if ( this.methods.length > 0 ) { + out.h(hl + 1, 'Methods'); + + for ( const method of this.methods ) { + out.h(hl + 2, '`' + method.key + '`'); + out (method.comment + '\n\n'); + + if ( method.params.length > 0 ) { + out.h(hl + 3, 'Parameters'); + for ( const param of method.params ) { + out(`- **${param.name}:** ${param.desc}\n`); + } + out.lf(); + } + } + } + return out.text(); } } diff --git a/tools/module-docgen/main.js b/tools/module-docgen/main.js index 4c1704827..12089a981 100644 --- a/tools/module-docgen/main.js +++ b/tools/module-docgen/main.js @@ -6,6 +6,7 @@ const rootdir = path_.resolve(process.argv[2] ?? '.'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const { ModuleDoc } = require("./defs"); +const processors = require("./processors"); const doc_module = new ModuleDoc(); @@ -27,71 +28,41 @@ for ( const file of files ) { 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 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; - - // Skip utility classes (for now) - if ( name !== file.slice(0, -3) ) { - return; - } - - const comment = (node.leadingComments && ( - node.leadingComments.length < 1 ? '' : - node.leadingComments[node.leadingComments.length - 1] - )) ?? ''; - - let doc_item = doc_module; - if ( type !== 'module' ) { - doc_item = doc_module.add_service(); - } - - doc_item.name = name; - if ( 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); - path.node.body.body.forEach(member => { - const key = member.key.name ?? member.key.value; - - const comment = member.leadingComments?.[0]?.value ?? ''; - - if ( key.startsWith('__on_') ) { - // 2nd argument is always an object destructuring; - // we want the list of keys in the object: - const params = member.params?.[1]?.properties ?? []; - - doc_item.provide_listener({ - key: key.slice(5), - comment, - params, - }); + 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 ) { + if ( ! traverse_callbacks[key] ) { + traverse_callbacks[key] = []; } - console.log(member.type, key, member.leadingComments); - }); + traverse_callbacks[key].push(processor.traverse[key]); + } } - }) + } + for ( const key in traverse_callbacks ) { + traverse(ast, { + [key] (path) { + for ( const callback of traverse_callbacks[key] ) { + callback(path, context); + } + } + }); + } } const outfile = path_.join(rootdir, 'README.md'); diff --git a/tools/module-docgen/processors.js b/tools/module-docgen/processors.js new file mode 100644 index 000000000..8063b940a --- /dev/null +++ b/tools/module-docgen/processors.js @@ -0,0 +1,94 @@ +const processors = []; + +processors.push({ + title: 'track all require calls', + match () { return true; }, + traverse: { + CallExpression (path, context) { + const callee = path.get('callee'); + if ( ! callee.isIdentifier() ) return; + + if ( callee.node.name === 'require' ) { + context.doc_module.requires.push(path.node.arguments[0].value); + } + } + } +}); + +processors.push({ + title: 'get leading comment', + match () { return true; }, + traverse: { + ClassDeclaration (path, context) { + const node = path.node; + const comment = (node.leadingComments && ( + node.leadingComments.length < 1 ? '' : + node.leadingComments[node.leadingComments.length - 1] + )) ?? ''; + context.comment = comment; + } + } +}); + +processors.push({ + title: 'provide name and comment for modules and services', + match (context) { + return context.type === 'module' || context.type === 'service'; + }, + traverse: { + ClassDeclaration (path, context) { + context.doc_item = context.doc_module; + if ( context.type === 'service' ) { + context.doc_item = context.doc_module.add_service(); + } + context.doc_item.name = path.node.id.name; + context.doc_item.provide_comment(context.comment); + } + } +}); + +processors.push({ + title: 'provide methods and listeners for services', + match (context) { + return context.type === 'service'; + }, + traverse: { + ClassDeclaration (path, context) { + path.node.body.body.forEach(member => { + if ( member.type !== 'ClassMethod' ) return; + + const key = member.key.name ?? member.key.value; + + const comment = member.leadingComments?.[0]?.value ?? ''; + + if ( key.startsWith('__on_') ) { + // 2nd argument is always an object destructuring; + // we want the list of keys in the object: + const params = member.params?.[1]?.properties ?? []; + + context.doc_item.provide_listener({ + key: key.slice(5), + comment, + params, + }); + } else { + // Method overrides + if ( key.startsWith('_') ) return; + + // Private methods + if ( key.endsWith('_') ) return; + + const params = member.params ?? []; + + context.doc_item.provide_method({ + key, + comment, + params, + }); + } + }); + } + } +}); + +module.exports = processors;