UNPKG

documentation

Version:
396 lines (371 loc) 11.2 kB
import { u } from 'unist-builder'; import { remark } from 'remark'; import mergeConfig from '../merge_config.js'; import toc from 'remark-toc'; import links from 'remark-reference-links'; import hljs from 'highlight.js'; import GithubSlugger from 'github-slugger'; import LinkerStack from './util/linker_stack.js'; import rerouteLinks from './util/reroute_links.js'; import _formatType from './util/format_type.js'; const DEFAULT_LANGUAGE = 'javascript'; /** * Given a hierarchy-nested set of comments, generate an remark-compatible * Abstract Syntax Tree usable for generating Markdown output * * @param comments nested comment * @param {Object} args currently none accepted * @param {boolean} [args.markdownToc=true] whether to include a table of contents * in markdown output. * @param {Object} [args.hljs={}] config to be passed to highlightjs for code highlighting: * consult hljs.configure for the full list. * @returns {Promise<Object>} returns an eventual Markdown value */ export default function markdownAST(comments, args) { return mergeConfig(args).then(config => buildMarkdownAST(comments, config)); } function buildMarkdownAST(comments, config) { // Configure code highlighting const hljsOptions = config.hljs || {}; hljs.configure(hljsOptions); const linkerStack = new LinkerStack(config).namespaceResolver( comments, namespace => { const slugger = new GithubSlugger(); return '#' + slugger.slug(namespace); } ); const formatType = _formatType.bind(undefined, linkerStack.link); const generatorComment = [ u( 'html', '<!-- Generated by documentation.js. Update this documentation by updating the source code. -->' ) ]; const tableOfContentsHeading = [ u('heading', { depth: 3 }, [u('text', 'Table of Contents')]) ]; /** * Generate an AST chunk for a comment at a given depth: this is * split from the main function to handle hierarchially nested comments * * @param {number} depth nesting of the comment, starting at 1 * @param {Object} comment a single comment * @returns {Object} remark-compatible AST */ function generate(depth, comment) { function typeSection(comment) { return ( comment.type && u('paragraph', [u('text', 'Type: ')].concat(formatType(comment.type))) ); } function paramList(params) { if (params.length === 0) return []; return u( 'list', { ordered: false, spread: false }, params.map(param => u( 'listItem', [ u( 'paragraph', [ u('inlineCode', param.name), u('text', ' '), !!param.type && u('strong', formatType(param.type)), u('text', ' ') ] .concat(param.description ? param.description.children : []) .concat([ !!param.default && u('paragraph', [ u('text', ' (optional, default '), u('inlineCode', param.default), u('text', ')') ]) ]) .filter(Boolean) ) ] .concat(param.properties && paramList(param.properties)) .filter(Boolean) ) ) ); } function paramSection(comment) { return ( comment.params.length > 0 && [ u('heading', { depth: depth + 1 }, [u('text', 'Parameters')]), paramList(comment.params) ] ); } function propertySection(comment) { return ( comment.properties.length > 0 && [ u('heading', { depth: depth + 1 }, [u('text', 'Properties')]), propertyList(comment.properties) ] ); } function propertyList(properties) { return u( 'list', { ordered: false, spread: false }, properties.map(property => u( 'listItem', [ u( 'paragraph', [ u('inlineCode', property.name), u('text', ' '), u('strong', formatType(property.type)), u('text', ' ') ] .concat( property.description ? property.description.children : [] ) .filter(Boolean) ), property.properties && propertyList(property.properties) ].filter(Boolean) ) ) ); } function examplesSection(comment) { return ( comment.examples.length > 0 && [u('heading', { depth: depth + 1 }, [u('text', 'Examples')])].concat( comment.examples.reduce(function (memo, example) { const language = hljsOptions.highlightAuto ? hljs.highlightAuto(example.description).language : DEFAULT_LANGUAGE; return memo .concat( example.caption ? [u('paragraph', [u('emphasis', example.caption)])] : [] ) .concat([u('code', { lang: language }, example.description)]); }, []) ) ); } function returnsSection(comment) { return ( comment.returns.length > 0 && comment.returns.map(returns => u( 'paragraph', [ u('text', 'Returns '), u('strong', formatType(returns.type)), u('text', ' ') ].concat(returns.description ? returns.description.children : []) ) ) ); } function throwsSection(comment) { return ( comment.throws.length > 0 && u( 'list', { ordered: false, spread: false }, comment.throws.map(returns => u('listItem', [ u( 'paragraph', [ u('text', 'Throws '), u('strong', formatType(returns.type)), u('text', ' ') ].concat( returns.description ? returns.description.children : [] ) ) ]) ) ) ); } function augmentsLink(comment) { return ( comment.augments.length > 0 && u('paragraph', [ u('strong', [ u('text', 'Extends '), u('text', comment.augments.map(tag => tag.name).join(', ')) ]) ]) ); } function seeLink({ sees = [] }) { return ( sees.length > 0 && u( 'list', { ordered: false, spread: false }, sees.map(see => u('listItem', [ u( 'paragraph', [u('strong', [u('text', 'See')]), u('text', ': ')].concat( see.description ) ) ]) ) ) ); } function githubLink(comment) { return ( comment.context && comment.context.github && u('paragraph', [ u( 'link', { title: 'Source code on GitHub', url: comment.context.github.url }, [ u( 'text', comment.context.github.path + ':' + comment.context.loc.start.line + '-' + comment.context.loc.end.line ) ] ) ]) ); } function metaSection(comment) { const meta = [ 'version', 'since', 'copyright', 'author', 'license', 'deprecated' ].filter(tag => comment[tag]); return ( !!meta.length && [u('strong', [u('text', 'Meta')])].concat( u( 'list', { ordered: false, spread: false }, meta.map(tag => { let metaContent; if (tag === 'copyright' || tag === 'deprecated') { metaContent = comment[tag]; } else { metaContent = u('text', comment[tag]); } return u('listItem', [ u('paragraph', [ u('strong', [u('text', tag)]), u('text', ': '), metaContent ]) ]); }) ) ) ); } if (comment.kind === 'note') { return [u('heading', { depth }, [u('text', comment.name || '')])] .concat(comment.description) .concat( !!comment.members.static.length && comment.members.static.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .filter(Boolean); } return [u('heading', { depth }, [u('text', comment.name || '')])] .concat(githubLink(comment)) .concat(augmentsLink(comment)) .concat(seeLink(comment)) .concat(comment.description ? comment.description.children : []) .concat(typeSection(comment)) .concat(paramSection(comment)) .concat(propertySection(comment)) .concat(examplesSection(comment)) .concat(throwsSection(comment)) .concat(returnsSection(comment)) .concat(metaSection(comment)) .concat( !!comment.members.global.length && comment.members.global.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .concat( !!comment.members.instance.length && comment.members.instance.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .concat( !!comment.members.static.length && comment.members.static.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .concat( !!comment.members.inner.length && comment.members.inner.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .concat( !!comment.members.events.length && comment.members.events.reduce( (memo, child) => memo.concat(generate(depth + 1, child)), [] ) ) .filter(Boolean); } let root = rerouteLinks( linkerStack.link, u( 'root', generatorComment .concat(config.markdownToc ? tableOfContentsHeading : []) .concat( comments.reduce( (memo, comment) => memo.concat(generate(2, comment)), [] ) ) ) ); const pluginRemark = remark(); if (config.markdownToc) pluginRemark.use(toc, { tight: true, maxDepth: config.markdownTocMaxDepth }); if (config.noReferenceLinks !== true) pluginRemark.use(links); root = pluginRemark.run(root); return Promise.resolve(root); }