瀏覽代碼

dev: improve module docgen

- fixes some bugs
- adds list of relative requires
KernelDeimos 5 月之前
父節點
當前提交
9e5429b9ab

+ 1 - 0
package-lock.json

@@ -17661,6 +17661,7 @@
       "dependencies": {
         "@babel/parser": "^7.26.2",
         "@babel/traverse": "^7.25.9",
+        "dedent": "^1.5.3",
         "doctrine": "^3.0.0"
       }
     },

+ 20 - 11
src/backend/src/modules/web/README.md

@@ -20,13 +20,7 @@ Initializes socket.io
 
 ###### Parameters
 
-- `server`:  The server to attach socket.io to.
-
-### WebModule
-
-undefined
-
-#### Listeners
+- **server:**  The server to attach socket.io to.
 
 ### WebServerService
 
@@ -53,7 +47,22 @@ If the `config.http_port` is set to 'auto', it will try to find an available por
 Once the server is up and running, it emits the 'start.webserver' and 'ready.webserver' events.
 If the `config.env` is set to 'dev' and `config.no_browser_launch` is false, it will open the Puter URL in the default browser.
 
-##### `start.webserver`
-
-
-
+## Notes
+
+### Outside Imports
+
+This module has external relative imports. When these are
+removed it may become possible to move this module to an
+extension.
+
+**Imports:**
+- `../../services/BaseService` (use.BaseService)
+- `../../api/eggspress.js`
+- `../../util/context.js`
+- `../../services/BaseService.js`
+- `../../config.js`
+- `../../middleware/auth.js`
+- `../../util/strutil.js`
+- `../../fun/dev-console-ui-utils.js`
+- `../../helpers.js`
+- `../../fun/logos.js`

+ 0 - 4
src/backend/src/modules/web/WebServerService.js

@@ -94,10 +94,6 @@ class WebServerService extends BaseService {
     *
     * @return {Promise} A promise that resolves when the server is up and running.
     */
-    // eslint-disable-next-line no-unused-vars
-    async ['__on_start.webserver'] () {
-    // ... rest of the method code
-    }
     async ['__on_start.webserver'] () {
         await es_import_promise;
 

+ 161 - 0
tools/module-docgen/defs.js

@@ -0,0 +1,161 @@
+const dedent = require('dedent');
+const doctrine = require('doctrine');
+
+class Out {
+    constructor () {
+        this.str = '';
+        const fn = this.out.bind(this);
+        fn.h = this.h.bind(this);
+        fn.lf = this.lf.bind(this);
+        fn.text = () => this.str;
+        return fn;
+    }
+    
+    h (n, text) {
+        this.str += '#'.repeat(n) + ' ' + text + '\n\n';
+    }
+    
+    lf () { this.str += '\n'; }
+    
+    out (str) {
+        this.str += str;
+    }
+}
+
+class Doc {
+    constructor () {
+        this._construct();
+    }
+    provide_comment (comment) {
+        const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
+        this.comment = parsed_comment.description;
+    }
+}
+
+class ModuleDoc extends Doc {
+    _construct () {
+        this.services = [];
+        this.requires = [];
+    }
+    
+    add_service () {
+        const service = new ServiceDoc();
+        this.services.push(service);
+        return service;
+    }
+    
+    ready () {
+        this.notes = [];
+        const rel_requires = this.requires.filter(r => r.startsWith('../'));
+        if ( rel_requires.length > 0 ) {
+            this.notes.push({
+                title: 'Outside Imports',
+                desc: dedent(`
+                    This module has external relative imports. When these are
+                    removed it may become possible to move this module to an
+                    extension.
+                    
+                    **Imports:**
+                    ${rel_requires.map(r => {
+                        let maybe_aside = '';
+                        if ( r.endsWith('BaseService') ) {
+                            maybe_aside = ' (use.BaseService)';
+                        }
+                        return `- \`${r}\`` + maybe_aside;
+                    }).join('\n')}
+                `)
+            });
+        }
+    }
+    
+    toMarkdown ({ hl, out } = { hl: 1 }) {
+        this.ready();
+
+        out = out ?? new Out();
+        
+        out.h(hl, this.name);
+        
+        out(this.comment + '\n\n');
+        
+        if ( this.services.length > 0 ) {
+            out.h(hl + 1, 'Services');
+            
+            for ( const service of this.services ) {
+                service.toMarkdown({ out, hl: hl + 2 });
+            }
+        }
+        
+        if ( this.notes.length > 0 ) {
+            out.h(hl + 1, 'Notes');
+            for ( const note of this.notes ) {
+                out.h(hl + 2, note.title);
+                out(note.desc);
+                out.lf();
+            }
+        }
+
+
+        return out.text();
+    }
+}
+
+class ServiceDoc extends Doc {
+    _construct () {
+        this.listeners = [];
+    }
+    
+    provide_comment (comment) {
+        const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
+        this.comment = parsed_comment.description;
+    }
+    
+    provide_listener (listener) {
+        const parsed_comment = doctrine.parse(listener.comment, { unwrap: true });
+        
+        const params = [];
+        for ( const tag of parsed_comment.tags ) {
+            if ( tag.title !== 'evtparam' ) continue;
+            const name = tag.description.slice(0, tag.description.indexOf(' '));
+            const desc = tag.description.slice(tag.description.indexOf(' '));
+            params.push({ name, desc })
+        }
+
+        this.listeners.push({
+            ...listener,
+            comment: parsed_comment.description,
+            params,
+        });
+    }
+    
+    toMarkdown ({ hl, out } = { hl: 1 }) {
+        out = out ?? new Out();
+        
+        out.h(hl, this.name);
+        
+        out(this.comment + '\n\n');
+        
+        if ( this.listeners.length > 0 ) {
+            out.h(hl + 1, 'Listeners');
+            
+            for ( const listener of this.listeners ) {
+                out.h(hl + 2, '`' + listener.key + '`');
+                out (listener.comment + '\n\n');
+                
+                if ( listener.params.length > 0 ) {
+                    out.h(hl + 3, 'Parameters');
+                    for ( const param of listener.params ) {
+                        out(`- **${param.name}:** ${param.desc}\n`);
+                    }
+                    out.lf();
+                }
+            }
+        }
+        
+        return out.text();
+    }
+}
+
+module.exports = {
+    ModuleDoc,
+    ServiceDoc,
+};

+ 25 - 90
tools/module-docgen/main.js

@@ -5,51 +5,9 @@ const rootdir = path_.resolve(process.argv[2] ?? '.');
 
 const parser = require('@babel/parser');
 const traverse = require('@babel/traverse').default;
-const doctrine = require('doctrine');
+const { ModuleDoc } = require("./defs");
 
-const def_module = {
-    services: [],
-};
-
-const to_module_add_service = (def_module, values) => {
-    def_module.services.push({
-        ...values,
-    });
-}
-
-const to_service_add_listener = (def_service, values) => {
-    const parsed_comment = doctrine.parse(values.comment, { unwrap: true });
-    
-    const params = [];
-    for ( const tag of parsed_comment.tags ) {
-        if ( tag.title !== 'evtparam' ) continue;
-        const name = tag.description.slice(0, tag.description.indexOf(' '));
-        const desc = tag.description.slice(tag.description.indexOf(' '));
-        params.push({ name, desc })
-    }
-
-    def_service.listeners.push({
-        ...values,
-        comment: parsed_comment.description,
-        params,
-    });
-};
-
-const to_service_add_comment = (def_service, comment) => {
-    console.log('comment', comment);
-    const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
-    def_service.comment = parsed_comment.description;
-};
-
-const to_module_add_comment = (def_module, comment) => {
-    console.log('comment', comment);
-    const parsed_comment = doctrine.parse(comment.value, { unwrap: true });
-    def_module.comment = parsed_comment.description;
-};
-
-const create_service = () => ({
-    listeners: []
-});
+const doc_module = new ModuleDoc();
 
 // List files in this directory
 const files = fs.readdirSync(rootdir);
@@ -67,16 +25,21 @@ for ( const file of files ) {
         
     if ( type === null ) continue;
     
-    const def_service = create_service();
-    
     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;
-            def_service.name = name;
             
             // Skip utility classes (for now)
             if ( name !== file.slice(0, -3) ) {
@@ -88,16 +51,24 @@ for ( const file of files ) {
                 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' ) {
-                def_module.name = name;
-                if ( comment !== '' ) {
-                    to_module_add_comment(def_module, comment);
-                }
                 return;
             }
+    
             
             if ( comment !== '' ) {
-                to_service_add_comment(def_service, comment);
+                doc_item.provide_comment(comment);
+                // to_service_add_comment(def_service, comment);
             }
             
             console.log('class', name);
@@ -111,7 +82,7 @@ for ( const file of files ) {
                     // we want the list of keys in the object:
                     const params = member.params?.[1]?.properties ?? [];
                 
-                    to_service_add_listener(def_service, {
+                    doc_item.provide_listener({
                         key: key.slice(5),
                         comment,
                         params,
@@ -119,48 +90,12 @@ for ( const file of files ) {
                 }
                 console.log(member.type, key, member.leadingComments);
             });
-            // module_info.services.push({
-            //     name,
-            //     file,
-            // });
         }
     })
-    
-    to_module_add_service(def_module, def_service);
-    // console.log('parsed?', parsed);
 }
 
-console.log('module', JSON.stringify(def_module, undefined, '  '));
-
 const outfile = path_.join(rootdir, 'README.md');
 
-let out = '';
-
-out += `# ${def_module.name}\n\n`;
-
-if ( def_module.comment ) {
-    out += `${def_module.comment}\n\n`;
-}
-
-out += '## Services\n\n';
-
-for ( const service of def_module.services ) {
-    out += `### ${service.name}\n\n`;
-    out += `${service.comment}\n\n`;
-    
-    out += '#### Listeners\n\n';
-    for ( const listener of service.listeners ) {
-        out += `##### \`${listener.key}\`\n\n`;
-        out += `${listener.comment}\n\n`;
-        
-        if ( listener.params.length > 0 ) {
-            out += '###### Parameters\n\n';
-            for ( const param of listener.params ) {
-                out += `- \`${param.name}\`: ${param.desc}\n`;
-            }
-            out += '\n';
-        }
-    }
-}
+const out = doc_module.toMarkdown();
 
 fs.writeFileSync(outfile, out);

+ 1 - 0
tools/module-docgen/package.json

@@ -12,6 +12,7 @@
   "dependencies": {
     "@babel/parser": "^7.26.2",
     "@babel/traverse": "^7.25.9",
+    "dedent": "^1.5.3",
     "doctrine": "^3.0.0"
   }
 }