From 73bc62ae8f0ae33db98996be4ca9ce95fa5166f1 Mon Sep 17 00:00:00 2001 From: Andy Stark <30621568+therealandeeee@users.noreply.github.com> Date: Fri, 18 May 2018 14:30:26 +0100 Subject: [PATCH] [ADF-2979] Updated doc tools to avoid erasing MD docs with blank JSDocs in services (#3347) * [ADF-2979] Update to ignore blank JSDocs for method signature * [ADF-2979] Finished adding blank JSDoc check for services --- tools/doc/doctool.config.json | 3 +- tools/doc/mdNav.js | 96 +++++++++++++++++++-- tools/doc/mdNav.ts | 92 +++++++++++++++++++-- tools/doc/templates/service.combyne | 4 +- tools/doc/tools/tsInfo.js | 92 ++++++++++++++++++++- tools/doc/tools/tsInfo.ts | 124 +++++++++++++++++++++++++++- 6 files changed, 390 insertions(+), 21 deletions(-) diff --git a/tools/doc/doctool.config.json b/tools/doc/doctool.config.json index 10a5f713c7..6eff020804 100644 --- a/tools/doc/doctool.config.json +++ b/tools/doc/doctool.config.json @@ -17,8 +17,7 @@ "toc" ], "dev": [ - "tsInfo", - "typeLinker" + "tsInfo" ] } } \ No newline at end of file diff --git a/tools/doc/mdNav.js b/tools/doc/mdNav.js index 8e389652a2..93e533152b 100644 --- a/tools/doc/mdNav.js +++ b/tools/doc/mdNav.js @@ -26,6 +26,34 @@ var MDNav = /** @class */ (function () { } return new MDNav(this.root, this.root.children.length); }; + MDNav.prototype.findAll = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + if (!this.root || !this.root.children) { + return []; + } + var result = []; + var currIndex = 0; + for (var i = this.pos; i < this.root.children.length; i++) { + var child = this.root.children[i]; + if (test(child)) { + if (currIndex === index) { + result.push(new MDNav(this.root, i)); + } + else { + currIndex++; + } + } + } + return result; + }; + MDNav.prototype.emph = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "emphasis" && test(h); + }, index); + }; MDNav.prototype.heading = function (test, index) { if (test === void 0) { test = function () { return true; }; } if (index === void 0) { index = 0; } @@ -33,6 +61,48 @@ var MDNav = /** @class */ (function () { return h.type === "heading" && test(h); }, index); }; + MDNav.prototype.html = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "html" && test(h); + }, index); + }; + MDNav.prototype.list = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "list" && test(h); + }, index); + }; + MDNav.prototype.listItem = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "listItem" && test(h); + }, index); + }; + MDNav.prototype.listItems = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.findAll(function (h) { + return h.type === "listItem" && test(h); + }, index); + }; + MDNav.prototype.paragraph = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "paragraph" && test(h); + }, index); + }; + MDNav.prototype.strong = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "strong" && test(h); + }, index); + }; MDNav.prototype.table = function (test, index) { if (test === void 0) { test = function () { return true; }; } if (index === void 0) { index = 0; } @@ -40,13 +110,6 @@ var MDNav = /** @class */ (function () { return h.type === "table" && test(h); }, index); }; - MDNav.prototype.text = function (test, index) { - if (test === void 0) { test = function () { return true; }; } - if (index === void 0) { index = 0; } - return this.find(function (h) { - return h.type === "text" && test(h); - }, index); - }; MDNav.prototype.tableRow = function (test, index) { if (test === void 0) { test = function () { return true; }; } if (index === void 0) { index = 0; } @@ -61,6 +124,13 @@ var MDNav = /** @class */ (function () { return h.type === "tableCell" && test(h); }, index); }; + MDNav.prototype.text = function (test, index) { + if (test === void 0) { test = function () { return true; }; } + if (index === void 0) { index = 0; } + return this.find(function (h) { + return h.type === "text" && test(h); + }, index); + }; Object.defineProperty(MDNav.prototype, "item", { get: function () { if (!this.root || !this.root.children) { @@ -89,6 +159,18 @@ var MDNav = /** @class */ (function () { enumerable: true, configurable: true }); + Object.defineProperty(MDNav.prototype, "value", { + get: function () { + if (this.item && this.item["value"]) { + return this.item.value; + } + else { + return undefined; + } + }, + enumerable: true, + configurable: true + }); return MDNav; }()); exports.MDNav = MDNav; diff --git a/tools/doc/mdNav.ts b/tools/doc/mdNav.ts index fdf01cac87..0138bf11b7 100644 --- a/tools/doc/mdNav.ts +++ b/tools/doc/mdNav.ts @@ -26,6 +26,38 @@ export class MDNav { } + findAll(test: (element: any) => boolean = () => true, index: number = 0): MDNav[] { + if (!this.root || !this.root.children) { + return []; + } + + let result = []; + + let currIndex = 0; + + for (let i = this.pos; i < this.root.children.length; i++) { + let child = this.root.children[i]; + + if (test(child)) { + if (currIndex === index) { + result.push(new MDNav(this.root, i)); + } else { + currIndex++; + } + } + } + + return result; + } + + + emph(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "emphasis" && test(h); + }, index); + } + + heading(test: (element: any) => boolean = () => true, index: number = 0): MDNav { return this.find((h) => { return h.type === "heading" && test(h); @@ -33,16 +65,49 @@ export class MDNav { } - table(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + html(test: (element: any) => boolean = () => true, index: number = 0): MDNav { return this.find((h) => { - return h.type === "table" && test(h); + return h.type === "html" && test(h); }, index); } - text(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + list(test: (element: any) => boolean = () => true, index: number = 0): MDNav { return this.find((h) => { - return h.type === "text" && test(h); + return h.type === "list" && test(h); + }, index); + } + + + listItem(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "listItem" && test(h); + }, index); + } + + listItems(test: (element: any) => boolean = () => true, index: number = 0): MDNav[] { + return this.findAll((h) => { + return h.type === "listItem" && test(h); + }, index); + } + + paragraph(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "paragraph" && test(h); + }, index); + } + + + strong(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "strong" && test(h); + }, index); + } + + + table(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "table" && test(h); }, index); } @@ -60,6 +125,14 @@ export class MDNav { }, index); } + + text(test: (element: any) => boolean = () => true, index: number = 0): MDNav { + return this.find((h) => { + return h.type === "text" && test(h); + }, index); + } + + get item(): any { if (!this.root || !this.root.children) { return undefined; @@ -75,7 +148,16 @@ export class MDNav { } - get childNav() { + get childNav(): MDNav { return new MDNav(this.item); } + + + get value(): any { + if (this.item && this.item["value"]) { + return this.item.value; + } else { + return undefined; + } + } } \ No newline at end of file diff --git a/tools/doc/templates/service.combyne b/tools/doc/templates/service.combyne index 78de73243a..5254efb138 100644 --- a/tools/doc/templates/service.combyne +++ b/tools/doc/templates/service.combyne @@ -2,10 +2,10 @@ {% if hasMethods %} ### Methods -{% each methods as meth %}- `{{meth.name}}{{{meth.signature}}{% if meth.returnsSomething %}: {{{meth.returnType}}}{% endif %}`
+{% each methods as meth %}- **{{meth.name}}**{{{meth.signature}}}{% if meth.returnsSomething %}: `{{{meth.returnType}}}`{% endif %}
{{meth.docText}} {% each meth.params as param %} - - `{{{param.combined}}}` - {% if param.isOptional %}(Optional){% endif %}{{{param.docText}}} + - *{{{param.name}}}:* `{{{param.type}}}` - {% if param.isOptional %}(Optional){% endif %}{{{param.docText}}} {% endeach %} {% if meth.returnsSomething %} - **Returns** `{{{meth.returnType}}}` - {{{meth.returnDocText}}} diff --git a/tools/doc/tools/tsInfo.js b/tools/doc/tools/tsInfo.js index 8c06d5c7e5..651a17ba7e 100644 --- a/tools/doc/tools/tsInfo.js +++ b/tools/doc/tools/tsInfo.js @@ -196,8 +196,6 @@ function aggPhase(aggData) { } exports.aggPhase = aggPhase; function updatePhase(tree, pathname, aggData, errorMessages) { - var inputMD = getPropDocsFromMD(tree, "Properties", 3); - var outputMD = getPropDocsFromMD(tree, "Events", 2); var compName = angNameToClassName(path.basename(pathname, ".md")); var classRef = aggData.projData.findReflectionByName(compName); if (!classRef) { @@ -208,7 +206,13 @@ function updatePhase(tree, pathname, aggData, errorMessages) { var classType = compName.match(/component|directive|service/i); if (classType) { // Copy docs back from the .md file when the JSDocs are empty. + var inputMD = getPropDocsFromMD(tree, "Properties", 3); + var outputMD = getPropDocsFromMD(tree, "Events", 2); updatePropDocsFromMD(compData, inputMD, outputMD, errorMessages); + if (classType === "service") { + var methodMD = getMethodDocsFromMD(tree); + updateMethodDocsFromMD(compData, methodMD, errorMessages); + } var templateName = path.resolve(templateFolder, classType + ".combyne"); var templateSource = fs.readFileSync(templateName, "utf8"); var template = combyne(templateSource); @@ -277,6 +281,74 @@ function getPropDocsFromMD(tree, sectionHeading, docsColumn) { } return result; } +function getMethodDocsFromMD(tree) { + var result = {}; + var nav = new mdNav_1.MDNav(tree); + var classMemHeading = nav + .heading(function (h) { + return (h.children[0].type === "text") && (h.children[0].value === "Class members"); + }); + var methListItems = classMemHeading + .heading(function (h) { + return (h.children[0].type === "text") && (h.children[0].value === "Methods"); + }).list().childNav; + var methItem = methListItems + .listItem(); + var i = 0; + while (!methItem.empty) { + var methNameSection = methItem.childNav + .paragraph().childNav + .strong().childNav; + var methName = ''; + // Method docs must be in "new" format with names and types styled separately. + if (!methNameSection.empty) { + methName = methNameSection.text().item.value; + var methDoc = methItem.childNav + .paragraph().childNav + .html() + .text().item.value; + var params = getMDMethodParams(methItem); + result[methName] = { + "docText": methDoc.replace(/^\n/, ""), + "params": params + }; + } + i++; + methItem = methListItems + .listItem(function (l) { return true; }, i); + } + /* + let newRoot = unist.makeRoot([methList.item]); + console.log(remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false, gfm: false}).stringify(tree)); + */ + return result; +} +function getMDMethodParams(methItem) { + var result = {}; + var paramList = methItem.childNav.list().childNav; + var paramListItems = paramList + .listItems(); + paramListItems.forEach(function (paramListItem) { + var paramNameNode = paramListItem.childNav + .paragraph().childNav + .emph().childNav; + var paramName; + if (!paramNameNode.empty) { + paramName = paramNameNode.text().item.value.replace(/:/, ""); + } + else { + paramName = paramListItem.childNav + .paragraph().childNav + .strong().childNav + .text().item.value; + } + var paramDoc = paramListItem.childNav + .paragraph().childNav + .text(function (t) { return true; }, 1).item.value; + result[paramName] = paramDoc.replace(/^[ -]+/, ""); + }); + return result; +} function updatePropDocsFromMD(comp, inputDocs, outputDocs, errorMessages) { comp.properties.forEach(function (prop) { var propMDDoc; @@ -293,3 +365,19 @@ function updatePropDocsFromMD(comp, inputDocs, outputDocs, errorMessages) { } }); } +function updateMethodDocsFromMD(comp, methodDocs, errorMessages) { + comp.methods.forEach(function (meth) { + var currMethMD = methodDocs[meth.name]; + // If JSDocs are empty but MD docs aren't then the Markdown is presumably more up-to-date. + if (!meth.docText && currMethMD.docText) { + meth.docText = currMethMD.docText; + errorMessages.push("Warning: empty JSDocs for method sig \"" + meth.name + "\" may need sync with the .md file."); + } + meth.params.forEach(function (param) { + if (!param.docText && currMethMD.params[param.name]) { + param.docText = currMethMD.params[param.name]; + errorMessages.push("Warning: empty JSDocs for parameter \"" + param.name + " (" + meth.name + ")\" may need sync with the .md file."); + } + }); + }); +} diff --git a/tools/doc/tools/tsInfo.ts b/tools/doc/tools/tsInfo.ts index 4ad471acb5..ec7b5bad28 100644 --- a/tools/doc/tools/tsInfo.ts +++ b/tools/doc/tools/tsInfo.ts @@ -285,9 +285,6 @@ export function aggPhase(aggData) { export function updatePhase(tree, pathname, aggData, errorMessages) { - let inputMD = getPropDocsFromMD(tree, "Properties", 3); - let outputMD = getPropDocsFromMD(tree, "Events", 2); - let compName = angNameToClassName(path.basename(pathname, ".md")); let classRef = aggData.projData.findReflectionByName(compName); @@ -301,7 +298,14 @@ export function updatePhase(tree, pathname, aggData, errorMessages) { if (classType) { // Copy docs back from the .md file when the JSDocs are empty. + let inputMD = getPropDocsFromMD(tree, "Properties", 3); + let outputMD = getPropDocsFromMD(tree, "Events", 2); updatePropDocsFromMD(compData, inputMD, outputMD, errorMessages); + + if (classType === "service") { + let methodMD = getMethodDocsFromMD(tree); + updateMethodDocsFromMD(compData, methodMD, errorMessages); + } let templateName = path.resolve(templateFolder, classType + ".combyne"); let templateSource = fs.readFileSync(templateName, "utf8"); @@ -401,6 +405,99 @@ function getPropDocsFromMD(tree, sectionHeading, docsColumn) { } +function getMethodDocsFromMD(tree) { + let result = {} + + let nav = new MDNav(tree); + + let classMemHeading = nav + .heading(h => { + return (h.children[0].type === "text") && (h.children[0].value === "Class members"); + }); + + let methListItems = classMemHeading + .heading(h => { + return (h.children[0].type === "text") && (h.children[0].value === "Methods"); + }).list().childNav; + + let methItem = methListItems + .listItem(); + + let i = 0; + + while (!methItem.empty) { + let methNameSection = methItem.childNav + .paragraph().childNav + .strong().childNav; + + let methName = ''; + + // Method docs must be in "new" format with names and types styled separately. + if (!methNameSection.empty) { + methName = methNameSection.text().item.value; + + let methDoc = methItem.childNav + .paragraph().childNav + .html() + .text().item.value; + + let params = getMDMethodParams(methItem); + + result[methName] = { + "docText": methDoc.replace(/^\n/, ""), + "params": params + }; + } + + i++; + + methItem = methListItems + .listItem(l=>true, i); + } + /* + let newRoot = unist.makeRoot([methList.item]); + console.log(remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false, gfm: false}).stringify(tree)); + */ + + return result; +} + + +function getMDMethodParams(methItem: MDNav) { + let result = {}; + + let paramList = methItem.childNav.list().childNav; + + let paramListItems = paramList + .listItems(); + + paramListItems.forEach(paramListItem => { + let paramNameNode = paramListItem.childNav + .paragraph().childNav + .emph().childNav; + + let paramName; + + if (!paramNameNode.empty) { + paramName = paramNameNode.text().item.value.replace(/:/, ""); + } else { + paramName = paramListItem.childNav + .paragraph().childNav + .strong().childNav + .text().item.value; + } + + let paramDoc = paramListItem.childNav + .paragraph().childNav + .text(t=>true, 1).item.value; + + result[paramName] = paramDoc.replace(/^[ -]+/, ""); + }); + + return result; +} + + function updatePropDocsFromMD(comp: ComponentInfo, inputDocs, outputDocs, errorMessages) { comp.properties.forEach(prop => { let propMDDoc: string; @@ -417,4 +514,25 @@ function updatePropDocsFromMD(comp: ComponentInfo, inputDocs, outputDocs, errorM errorMessages.push(`Warning: empty JSDocs for property "${prop.name}" may need sync with the .md file.`); } }); +} + + +function updateMethodDocsFromMD(comp: ComponentInfo, methodDocs, errorMessages) { + comp.methods.forEach(meth => { + let currMethMD = methodDocs[meth.name] + + // If JSDocs are empty but MD docs aren't then the Markdown is presumably more up-to-date. + if (!meth.docText && currMethMD.docText) { + meth.docText = currMethMD.docText; + errorMessages.push(`Warning: empty JSDocs for method sig "${meth.name}" may need sync with the .md file.`); + } + + meth.params.forEach(param => { + if (!param.docText && currMethMD.params[param.name]) + { + param.docText = currMethMD.params[param.name]; + errorMessages.push(`Warning: empty JSDocs for parameter "${param.name} (${meth.name})" may need sync with the .md file.`); + } + }); + }); } \ No newline at end of file