ts2md
Version:
Simple Typescript Documentation Generator to GitHub Compatible MarkDown
253 lines • 9.87 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypescriptToMarkdown = void 0;
const os_1 = require("os");
const path = __importStar(require("path"));
const typescript_1 = __importDefault(require("typescript"));
const mdMerge_1 = require("./mdMerge");
const Docs_1 = require("./Docs");
/**
* Uses the Typescript compiler to parse source tree given a top level source file such as `index.ts`.
*
* Extract the exported API interfaces, classes, types, functions and variables.
*
* Generate GitHub friendly MarkDown documentation for the extracted API leveraging TypeScript type information
* and merging JSDoc style documentation comments.
*
* The following JSDoc tags are supported:
*
* `@example` Adds example as code block or comments and embedded code block(s).
*
* `@param` Adds comment for function or method parameter.
*
* `@private` Hides an otherwise accessible documentation item.
*
* `@privateinitializer` Hides property initializer from documentation typescript.
*
* `@property` Adds comment for class or interface property parameter in parent's JSDoc comment.
*
* `@publicbody` Overrides the normal hidding of method and function bodies.
*
* `@returns` Adds comment for function or method return value.
*
* `@throws` Adds thrown error comment to function or method.
*/
class TypescriptToMarkdown {
/**
* Construct a new instance configured for `run` method to be called next.
*
* @param options Must be provided. inputFilename defaults to `./src/index.ts`
*/
constructor(options, mdLinksEx) {
this.options = options;
this.mdLinksEx = mdLinksEx;
this.sourceFiles = [];
this.docs = [];
/**
* For each symbol name, the markdown link which will exist in this document.
*/
this.mdLinks = {};
/**
* mdLinks that are not defined in the current outputPath
*/
this.mdLinksExternal = {};
options.inputFilename || (options.inputFilename = './src/index.ts');
this.nothingPrivate = options.nothingPrivate || false;
this.noDetailsSummary = options.noDetailsSummary || false;
this.filePath = path.resolve(options.inputFilename);
this.fileName = path.parse(this.filePath).name;
const compOpts = {
allowJs: false,
target: typescript_1.default.ScriptTarget.Latest
};
const progOpts = {
rootNames: [this.filePath],
options: compOpts
};
this.program = typescript_1.default.createProgram(progOpts);
const printOpts = {
newLine: os_1.EOL === '\n' ? typescript_1.default.NewLineKind.LineFeed : typescript_1.default.NewLineKind.CarriageReturnLineFeed,
removeComments: true,
omitTrailingSemicolon: true
};
const printHandlers = {};
this.printer = typescript_1.default.createPrinter(printOpts, printHandlers);
}
/**
* Generates the documentation markdown and write's it to output file
* and/or merges it to README.md
*/
run() {
this.outputPath = path.resolve(this.options.outputFilename || './README.md');
const requireAnchors = !this.options.outputFilename;
this.sourceFiles = this.parseSourceFiles(this.program);
this.docs = this.extractDocs(this.sourceFiles);
this.markDown = this.generateMarkDown(this.docs);
if (this.markDown) {
(0, mdMerge_1.mdMerge)(this.markDown, this.outputPath, requireAnchors);
}
return {
outputPath: this.outputPath,
mdLinks: this.mdLinks,
mdLinksExternal: this.mdLinksExternal
};
}
/**
* @private
*/
headingLevelMd(relativeLevel) {
relativeLevel += this.options.firstHeadingLevel - 1;
if (this.options.noTitle)
relativeLevel--;
return '#'.repeat(relativeLevel + this.options.firstHeadingLevel - 1);
}
parseSourceFiles(program) {
return program.getSourceFiles().filter(sf => sf.fileName.indexOf('node_modules') === -1);
}
extractDocs(sourceFiles) {
let docs = [
new Docs_1.DocInterface(this),
new Docs_1.DocClass(this),
new Docs_1.DocFunction(this),
new Docs_1.DocType(this),
new Docs_1.DocEnum(this),
new Docs_1.DocVariable(this)
];
for (const sf of sourceFiles) {
//console.log(`SourceFile ${sf.fileName}`);
typescript_1.default.forEachChild(sf, node => {
if (typescript_1.default.isStatement(node)) {
for (const doc of docs) {
doc.tryAddItem(node, sf);
}
}
});
}
// Eliminate empty doc categories
docs = docs.filter(d => d.docItems.length > 0);
return docs;
}
generateMarkDown(docs) {
let md = '';
this.mdLinks = {};
this.mdLinksExternal = {};
const contributesToOutput = (docItem) => {
if (!this.options.filenameSubString)
return true;
const fn = docItem.sf.fileName;
const add = fn.indexOf(this.options.filenameSubString || '') > -1;
return add;
};
// Sort docItems by name
for (const doc of docs) {
doc.docItems = doc.docItems.sort((a, b) => {
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
});
}
for (const doc of this.docs) {
const filteredItems = [];
for (const docItem of doc.docItems) {
const linkMd = doc.toMarkDownRefLink(docItem);
if (contributesToOutput(docItem)) {
this.mdLinks[docItem.name] = linkMd;
filteredItems.push(docItem);
//console.log(`Adding ${fn} ${docItem.name}`)
}
else {
if (!this.mdLinksExternal[docItem.name])
this.mdLinksExternal[docItem.name] = linkMd;
}
}
doc.docItems = filteredItems;
}
if (this.mdLinksEx)
this.mdLinks = this.mdLinksEx;
if (!this.options.noTitle) {
md += `${this.headingLevelMd(1)} API${os_1.EOL}${os_1.EOL}`;
}
else {
md += os_1.EOL;
}
let linksMd = `Links: [API](#api)`;
for (const doc of docs) {
linksMd += `, [${doc.labelPlural}](#${doc.labelPlural.toLowerCase()})`;
}
linksMd += os_1.EOL + os_1.EOL;
md += linksMd;
for (const doc of this.docs) {
md += `${this.headingLevelMd(2)} ${doc.labelPlural}` + os_1.EOL + os_1.EOL;
if (doc.docItems.length > 1) {
md += this.generateDocItemLinksTable(doc);
md += os_1.EOL + `${linksMd}---` + os_1.EOL + os_1.EOL;
}
for (const docItem of doc.docItems) {
md += doc.toMarkDown(docItem, this.mdLinks);
md += linksMd;
md += '---' + os_1.EOL;
}
}
return md;
}
/**
* Generates a markdown table of named links to docItem documentation headers.
* @param doc The category of documentation for which to generate the table.
* @returns markdown table of named links to docItem documentation headers
*/
generateDocItemLinksTable(doc) {
let md = '';
const nameLinks = doc.docItems.map(d => ({ name: d.name, linkMd: doc.toMarkDownRefLink(d) }));
nameLinks.sort((a, b) => a.name < b.name ? -1 : a.name === b.name ? 0 : 1);
let cols = 1, rows = nameLinks.length;
while (cols < 3 && rows > 12) {
cols++;
rows = Math.ceil(nameLinks.length / cols);
}
for (let col = 1; col <= cols; col++) {
md += '| ';
}
md += '|' + os_1.EOL;
for (let col = 1; col <= cols; col++) {
md += '| --- ';
}
md += '|' + os_1.EOL;
for (let row = 1; row <= rows; row++) {
for (let col = 1; col <= cols; col++) {
const i = (col - 1) * rows + row - 1;
const d = i < nameLinks.length ? nameLinks[i].linkMd : '';
if (col === 1)
md += '|';
md += ` ${d} |`;
}
md += os_1.EOL + '';
}
return md;
}
}
exports.TypescriptToMarkdown = TypescriptToMarkdown;
//# sourceMappingURL=TypescriptToMarkdown.js.map