UNPKG

remark-api

Version:

remark plugin to generate an API section

110 lines (93 loc) 3.12 kB
/** * @typedef {import('mdast').Root} Root * @typedef {import('mdast').RootContent} RootContent * * @typedef {import('unist').Position} Position * * @typedef {import('vfile').VFile} VFile * * @typedef {import('vfile-message').VFileMessage} VFileMessage */ import path from 'node:path' import {fileURLToPath, pathToFileURL} from 'node:url' import {headingRange} from 'mdast-util-heading-range' import {packageExports} from 'package-exports' import {createFinder, defaultFormat} from 'module-exports' import {findUp} from 'vfile-find-up' /** * Generate an API section. * * Looks for the closest `package.json` file upwards from the current * markdown file. * For each export in the package, it generates API docs. * It injects those into an `# API` section. * * @returns * Transform. */ export default function remarkApi() { const finder = createFinder() // If there is no export maps, and no `files`, then everything is exported. // That includes markdown and `license` and `.gitignore` and whatever. // So, for now, just ignore all that and only allow JS. const allowedExtensionNames = new Set(['cjs', 'js', 'mjs']) /** * @param {Root} tree * @param {VFile} file * @returns {Promise<undefined>} */ return async function (tree, file) { /* c8 ignore next -- else is for stdin, typically not used. */ const base = file.dirname ? path.resolve(file.cwd, file.dirname) : file.cwd const packageFile = await findUp('package.json', base) if (!packageFile) { return } const folderUrl = new URL('.', pathToFileURL(packageFile.path)) const exportsResult = await packageExports(folderUrl) /** @type {Array<RootContent>} */ const content = [] /** @type {Set<string>} */ const seen = new Set() /** @type {Array<[reason: string, cause: VFileMessage]>} */ const allMessages = [] for (const exported of exportsResult.exports) { const extensionName = path.extname(exported.url).slice(1) if (!allowedExtensionNames.has(extensionName)) { continue } if (seen.has(exported.url)) { continue } seen.add(exported.url) const url = new URL(exported.url) const result = await finder(url) const file = fileURLToPath(url) const reason = 'Unexpected warning processing `' + path.relative(base, file) + '`' for (const message of result.messages) { // To do: move to `module-exports`? if (!message.file) message.file = file allMessages.push([reason, message]) } const formatted = await defaultFormat(result.symbols) content.push(...formatted.children) } headingRange( tree, {ignoreFinalDefinitions: true, test: 'api'}, function (start, _, end) { for (const [reason, cause] of allMessages) { file.message(reason, { ancestors: [tree, start], cause, place: start.position, ruleId: 'message', source: 'remark-api' }) } return [start, ...content, end] } ) } }