UNPKG

mixpanel-browser

Version:

The official Mixpanel JavaScript browser client library

121 lines (106 loc) 4.29 kB
/** * A tool to generate Markdown documentation from JSDoc comments in the SDK source, * in a format suited for publication on readme.io. * * The general pipeline is: * bundled source (mixpanel.js) -> dox -> data transformation -> .md template -> compiled .md * * The rendered doc is published with https://www.npmjs.com/package/rdme and lives at * https://developer.mixpanel.com/docs/javascript-full-api-reference */ const dox = require(`dox`); const fs = require(`fs`); const {mapValues, template, trim} = require(`lodash`); const path = require(`path`); const SOURCE_FILE = path.join(__dirname, `..`, `mixpanel.js`); const TEMPLATE_FILE = path.join(__dirname, `template.md`); const OUTPUT_FILE = path.join(__dirname, `readme.io`, `javascript-full-api-reference.md`); const NAMESPACES = { MixpanelLib: `mixpanel`, MixpanelPeople: `mixpanel.people`, MixpanelGroup: `mixpanel.group`, }; // dox generates some HTML markup automatically out of JSDoc description blocks. We parse a few // sections out of these blocks with the following RegExps const DESCRIPTION_REGEXES = { description: /([\S\s]+?)(<h3>|$)/, usage: /<h3>Usage:<\/h3>([\S\s]+?)(<h3>|$)/, notes: /<h3>Notes:<\/h3>([\S\s]+?)(<h3>|$)/, }; function parseDescriptionAttrs(html) { return mapValues(DESCRIPTION_REGEXES, regex => { const match = html.match(regex); return match && match[1] .trim() .replace(/<br \/>/g, ` `) .replace(/<p>([\S\s]+?)<\/p>/g, (str, match) => match.replace(/\n/g, ` `) + `\n`) .replace(/<pre><code>([\s\S]+?)<\/code><\/pre>/g, '\n```javascript\n$1\n```\n') ; }); } function stripPTags(str) { return str.replace(/<p>([\S\s]+?)<\/?p>/g, `$1`); } function isDeprecated(item) { return item.tags.find(tag => tag.type === `deprecated`); } // transform the structured data dox parses out of our JSDoc and feed it through // the lodash template at template.md function doxToMD(items) { const renderMD = template(fs.readFileSync(TEMPLATE_FILE).toString()); return renderMD({ namespaces: Object.entries(NAMESPACES).map(([constructor, namespace]) => ({ name: namespace, items: items // filter down to public methods of the current namespace/class .filter(item => !item.isPrivate && item.ctx && !item.ctx.name.startsWith(`_`) && item.ctx.constructor === constructor && !isDeprecated(item) ) // sort by method name within each namespace .sort((a, b) => a.ctx.name > b.ctx.name ? 1 : -1) // transform each method's data into the format we want (for instance stripping out // <p> tags and adding a `required` field rather than [] around param names) .map(item => ({ name: `${namespace}.${item.ctx.name}`, arguments: item.tags .filter(arg => !!arg.name) .map(arg => ({ name: trim(arg.name, `[]`), description: stripPTags(arg.description), required: !arg.name.startsWith(`[`), types: arg.typesDescription === `<code>*</code>` ? `any` : arg.types.join(` or `), })), returns: item.tags .filter(tag => tag.type === `returns`) .map(tag => ({ description: stripPTags(tag.description), types: tag.types.join(` or `), })), ...parseDescriptionAttrs(item.description.full), })), })), }); } // Captures prototype bracket notation property assignment, e.g.: // `MixpanelGroup.prototype['delete'] = addOptOutCheckMixpanelGroup(function(callback) {` // Based on https://github.com/tj/dox/blob/9fe92e17dfcd31c9b6512f6e5bf0b52c2b6b84d4/lib/dox.js#L592 dox.contextPatternMatchers.push(function (str) { if (/^\s*([\w$.]+)\s*\.\s*prototype\s*\['\s*([\w$]+)'\]\s*=\s*([^\n;]+)/.exec(str)) { return { type: 'property' , constructor: RegExp.$1 , cons: RegExp.$1 , name: RegExp.$2 , value: RegExp.$3.trim() , string: RegExp.$1 + '.prototype.' + RegExp.$2 }; } }); const rawCode = fs.readFileSync(SOURCE_FILE).toString().trim(); const parsed = dox.parseComments(rawCode); fs.writeFileSync(OUTPUT_FILE, doxToMD(parsed)); console.log(`Wrote docs to ${OUTPUT_FILE}`);