Bladeren bron

dev: include library functions in module docgen

KernelDeimos 5 maanden geleden
bovenliggende
commit
5c3a65060d
3 gewijzigde bestanden met toevoegingen van 188 en 32 verwijderingen
  1. 67 1
      tools/module-docgen/defs.js
  2. 71 31
      tools/module-docgen/main.js
  3. 50 0
      tools/module-docgen/processors.js

+ 67 - 1
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,

+ 71 - 31
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();

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