1
0
Эх сурвалжийг харах

docs(server): Correctly handle interface methods

Michael Bromley 7 жил өмнө
parent
commit
21c8a4e666
1 өөрчлөгдсөн 121 нэмэгдсэн , 40 устгасан
  1. 121 40
      codegen/generate-docs.ts

+ 121 - 40
codegen/generate-docs.ts

@@ -3,33 +3,65 @@ import klawSync from 'klaw-sync';
 import path from 'path';
 import path from 'path';
 import ts from 'typescript';
 import ts from 'typescript';
 
 
+import { notNullOrUndefined } from '../shared/shared-utils';
+
 // tslint:disable:no-console
 // tslint:disable:no-console
+interface MethodParameterInfo {
+    name: string;
+    type: string;
+}
+
 interface MemberInfo {
 interface MemberInfo {
     name: string;
     name: string;
     description: string;
     description: string;
     type: string;
     type: string;
+}
+
+interface PropertyInfo extends MemberInfo {
+    kind: 'property';
     defaultValue: string;
     defaultValue: string;
 }
 }
 
 
+interface MethodInfo extends MemberInfo {
+    kind: 'method';
+    parameters: MethodParameterInfo[];
+}
+
 interface InterfaceInfo {
 interface InterfaceInfo {
     title: string;
     title: string;
     weight: number;
     weight: number;
     category: string;
     category: string;
     description: string;
     description: string;
     fileName: string;
     fileName: string;
-    members: MemberInfo[];
+    members: Array<PropertyInfo | MethodInfo>;
 }
 }
 
 
+type TypeMap = Map<string, string>;
+
 const docsPath = '/docs/api/';
 const docsPath = '/docs/api/';
 const outputPath = path.join(__dirname, '../docs/content/docs/api');
 const outputPath = path.join(__dirname, '../docs/content/docs/api');
 const vendureConfig = path.join(__dirname, '../server/src/config/vendure-config.ts');
 const vendureConfig = path.join(__dirname, '../server/src/config/vendure-config.ts');
+const globalTypeMap: TypeMap = new Map();
+const tsFiles = klawSync(path.join(__dirname, '../server/src/'), {
+    nodir: true,
+    filter: item => {
+        return path.extname(item.path) === '.ts';
+    },
+    traverseAll: true,
+}).map(item => item.path);
 
 
 deleteGeneratedDocs();
 deleteGeneratedDocs();
-generateDocs();
-console.log(`Watching for changes to ${vendureConfig}`);
-fs.watchFile(vendureConfig, { interval: 1000 }, () => {
-    generateDocs();
-});
+generateDocs(tsFiles, globalTypeMap);
+
+const watchMode = !!process.argv.find(arg => arg === '--watch' || arg === '-w');
+if (watchMode) {
+    console.log(`Watching for changes to source files...`);
+    tsFiles.forEach(file => {
+        fs.watchFile(file, {interval: 1000}, () => {
+            generateDocs([file], globalTypeMap);
+        });
+    });
+}
 
 
 /**
 /**
  * Delete all generated docs found in the outputPath.
  * Delete all generated docs found in the outputPath.
@@ -47,43 +79,67 @@ function deleteGeneratedDocs() {
     console.log(`Deleted ${deleteCount} generated docs`);
     console.log(`Deleted ${deleteCount} generated docs`);
 }
 }
 
 
+/**
+ * Returns true if the content matches that of a generated document.
+ */
 function isGenerated(content: string) {
 function isGenerated(content: string) {
     return /generated\: true\n---\n/.test(content);
     return /generated\: true\n---\n/.test(content);
 }
 }
 
 
-function generateDocs() {
+function generateDocs(filePaths: string[], typeMap: TypeMap) {
     const timeStart = +new Date();
     const timeStart = +new Date();
-    const sourceFile = ts.createSourceFile(
-        vendureConfig,
-        fs.readFileSync(vendureConfig).toString(),
-        ts.ScriptTarget.ES2015,
-        true,
-    );
-    const knownTypeMap = new Map<string, string>();
-
-    const interfaces = [...sourceFile.statements]
+    const sourceFiles = filePaths.map(filePath => {
+        return ts.createSourceFile(
+            filePath,
+            fs.readFileSync(filePath).toString(),
+            ts.ScriptTarget.ES2015,
+            true,
+        );
+    });
+
+    const statements = sourceFiles.reduce((st, sf) => [...st, ...sf.statements], [] as ts.Statement[]);
+    const interfaces = statements
         .filter(ts.isInterfaceDeclaration)
         .filter(ts.isInterfaceDeclaration)
         .map(statement => {
         .map(statement => {
             const info = parseInterface(statement);
             const info = parseInterface(statement);
-            knownTypeMap.set(info.title, info.category + '/' + info.fileName);
+            if (info) {
+                typeMap.set(info.title, info.category + '/' + info.fileName);
+            }
             return info;
             return info;
-        });
+        })
+        .filter(notNullOrUndefined);
 
 
     for (const info of interfaces) {
     for (const info of interfaces) {
-        const markdown = renderInterface(info, knownTypeMap);
-        fs.writeFileSync(path.join(outputPath, info.category, info.fileName + '.md'), markdown);
+        const markdown = renderInterface(info, typeMap);
+        const categoryDir = path.join(outputPath, info.category);
+        const indexFile = path.join(categoryDir, '_index.md');
+        if (!fs.existsSync(categoryDir)) {
+            fs.mkdirSync(categoryDir);
+        }
+        if (!fs.existsSync(indexFile)) {
+            const indexFileContent = generateFrontMatter(info.category, 10) + `\n\n# ${info.category}`;
+            fs.writeFileSync(indexFile, indexFileContent);
+        }
+
+
+        fs.writeFileSync(path.join(categoryDir, info.fileName + '.md'), markdown);
     }
     }
 
 
-    console.log(`Generated ${interfaces.length} docs in ${+new Date() - timeStart}ms`);
+    if (interfaces.length) {
+        console.log(`Generated ${interfaces.length} docs in ${+new Date() - timeStart}ms`);
+    }
 }
 }
 
 
 /**
 /**
  * Parses an InterfaceDeclaration into a simple object which can be rendered into markdown.
  * Parses an InterfaceDeclaration into a simple object which can be rendered into markdown.
  */
  */
-function parseInterface(statement: ts.InterfaceDeclaration): InterfaceInfo {
+function parseInterface(statement: ts.InterfaceDeclaration): InterfaceInfo | undefined {
+    const category = getDocsCategory(statement);
+    if (category === undefined) {
+        return;
+    }
     const title = statement.name.text;
     const title = statement.name.text;
     const weight = getInterfaceWeight(statement);
     const weight = getInterfaceWeight(statement);
-    const category = getDocsCategory(statement);
     const description = getInterfaceDescription(statement);
     const description = getInterfaceDescription(statement);
     const fileName = title.split(/(?=[A-Z])/).join('-').toLowerCase();
     const fileName = title.split(/(?=[A-Z])/).join('-').toLowerCase();
     const members = parseMembers(statement.members);
     const members = parseMembers(statement.members);
@@ -100,15 +156,16 @@ function parseInterface(statement: ts.InterfaceDeclaration): InterfaceInfo {
 /**
 /**
  * Parses an array of inteface members into a simple object which can be rendered into markdown.
  * Parses an array of inteface members into a simple object which can be rendered into markdown.
  */
  */
-function parseMembers(members: ts.NodeArray<ts.TypeElement>): MemberInfo[] {
-    const result: MemberInfo[] = [];
+function parseMembers(members: ts.NodeArray<ts.TypeElement>): Array<PropertyInfo | MethodInfo> {
+    const result: Array<PropertyInfo | MethodInfo> = [];
 
 
     for (const member of members) {
     for (const member of members) {
-        if (ts.isPropertySignature(member)) {
+        if (ts.isPropertySignature(member) || ts.isMethodSignature(member)) {
             const name = member.name.getText();
             const name = member.name.getText();
             let description = '';
             let description = '';
             let type = '';
             let type = '';
             let defaultValue = '';
             let defaultValue = '';
+            let parameters: MethodParameterInfo[] = [];
             parseTags(member, {
             parseTags(member, {
                 description: tag => description += tag.comment || '',
                 description: tag => description += tag.comment || '',
                 example: tag => description += formatExampleCode(tag.comment),
                 example: tag => description += formatExampleCode(tag.comment),
@@ -117,13 +174,27 @@ function parseMembers(members: ts.NodeArray<ts.TypeElement>): MemberInfo[] {
             if (member.type) {
             if (member.type) {
                 type = member.type.getFullText();
                 type = member.type.getFullText();
             }
             }
-
-            result.push({
-                name,
-                description,
-                type,
-                defaultValue,
-            });
+            if (ts.isMethodSignature(member)) {
+                parameters = member.parameters.map(p => ({
+                    name: p.name.getText(),
+                    type: p.type ? p.type.getFullText() : '',
+                }));
+                result.push({
+                    kind: 'method',
+                    name,
+                    description,
+                    type,
+                    parameters,
+                });
+            } else {
+                result.push({
+                    kind: 'property',
+                    name,
+                    description,
+                    type,
+                    defaultValue,
+                });
+            }
         }
         }
     }
     }
 
 
@@ -138,9 +209,19 @@ function renderInterface(interfaceInfo: InterfaceInfo, knownTypeMap: Map<string,
     output += `${description}\n\n`;
     output += `${description}\n\n`;
 
 
     for (const member of members) {
     for (const member of members) {
-        const type = renderType(member.type, knownTypeMap);
+        let defaultParam = '';
+        let type = '';
+        if (member.kind === 'property') {
+            type = renderType(member.type, knownTypeMap);
+            defaultParam = member.defaultValue ? `default="${member.defaultValue}" ` : '';
+        } else {
+            const args = member.parameters.map(p => {
+                return `${p.name}: ${renderType(p.type, knownTypeMap)}`;
+            }).join(', ');
+            type = `(${args}) => ${renderType(member.type, knownTypeMap)}`;
+        }
         output += `### ${member.name}\n\n`;
         output += `### ${member.name}\n\n`;
-        output += `{{< member-info type="${type}" ${member.defaultValue ? `default="${member.defaultValue}" ` : ''}>}}\n\n`;
+        output += `{{< member-info type="${type}" ${defaultParam}>}}\n\n`;
         output += `${member.description}\n\n`;
         output += `${member.description}\n\n`;
     }
     }
 
 
@@ -150,8 +231,8 @@ function renderInterface(interfaceInfo: InterfaceInfo, knownTypeMap: Map<string,
 /**
 /**
  * Extracts the "@docsCategory" value from the JSDoc comments if present.
  * Extracts the "@docsCategory" value from the JSDoc comments if present.
  */
  */
-function getDocsCategory(statement: ts.InterfaceDeclaration): string {
-    let category = '';
+function getDocsCategory(statement: ts.InterfaceDeclaration): string | undefined {
+    let category: string | undefined;
     parseTags(statement, {
     parseTags(statement, {
         docsCategory: tag => category = tag.comment || '',
         docsCategory: tag => category = tag.comment || '',
     });
     });
@@ -173,10 +254,10 @@ function parseTags<T extends ts.Node>(node: T, tagMatcher: { [tagName: string]:
 
 
 function renderType(type: string, knownTypeMap: Map<string, string>): string {
 function renderType(type: string, knownTypeMap: Map<string, string>): string {
     let typeText = type.trim().replace(/[\u00A0-\u9999<>\&]/gim, i => {
     let typeText = type.trim().replace(/[\u00A0-\u9999<>\&]/gim, i => {
-       return '&#' + i.charCodeAt(0) + ';';
-    });
+        return '&#' + i.charCodeAt(0) + ';';
+    }).replace(/\n/, ' ');
     for (const [key, val] of knownTypeMap) {
     for (const [key, val] of knownTypeMap) {
-        typeText = typeText.replace(key, `<a href='${docsPath}${val}/'>${key}</a>`);
+        typeText = typeText.replace(key, `<a href='${docsPath}/${val}/'>${key}</a>`);
     }
     }
     return typeText;
     return typeText;
 }
 }