UNPKG

ts2md

Version:

Simple Typescript Documentation Generator to GitHub Compatible MarkDown

253 lines 9.87 kB
"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