浏览代码

dev: cleanup docgen and add method logging

KernelDeimos 5 月之前
父节点
当前提交
5fa0a0263d
共有 3 个文件被更改,包括 162 次插入61 次删除
  1. 36 0
      tools/module-docgen/defs.js
  2. 32 61
      tools/module-docgen/main.js
  3. 94 0
      tools/module-docgen/processors.js

+ 36 - 0
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();
     }
 }

+ 32 - 61
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 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 ?? '';
+    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));
+    }
 
-                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 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 ) {
+                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');

+ 94 - 0
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;