Gatsby setup for API docs
This commit is contained in:
15
config/jsdoc/api-info/plugins/api.js
Normal file
15
config/jsdoc/api-info/plugins/api.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
/**
|
||||
* Handle the api annotation.
|
||||
* @param {Object} dictionary The tag dictionary.
|
||||
*/
|
||||
exports.defineTags = dictionary => {
|
||||
|
||||
dictionary.defineTag('api', {
|
||||
onTagged: (doclet, tag) => {
|
||||
doclet.api = true;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
170
config/jsdoc/api-info/plugins/module.js
Normal file
170
config/jsdoc/api-info/plugins/module.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* This plugin adds an `exportMap` property to @module doclets. Each export map
|
||||
* is an object with properties named like the local identifier and values named
|
||||
* like the exported identifier.
|
||||
*
|
||||
* For example, the code below
|
||||
*
|
||||
* export {foo as bar};
|
||||
*
|
||||
* would be a map like `{foo: 'bar'}`.
|
||||
*
|
||||
* In the case of an export declaration with a source, the export identifier is
|
||||
* prefixed by the source. For example, this code
|
||||
*
|
||||
* export {foo as bar} from 'ol/bam';
|
||||
*
|
||||
* would be a map like `{'ol/bam foo': 'bar'}`.
|
||||
*
|
||||
* If a default export is a literal or object expression, the local name will be
|
||||
* an empty string. For example
|
||||
*
|
||||
* export default {foo: 'bar'};
|
||||
*
|
||||
* would be a map like `{'': 'default'}`.
|
||||
*/
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
/**
|
||||
* A lookup of export maps per source filepath.
|
||||
*/
|
||||
const exportMapLookup = {};
|
||||
|
||||
function loc(filepath, node) {
|
||||
return `${filepath}:${node.loc.start.line}`;
|
||||
}
|
||||
|
||||
function nameFromChildIdentifier(filepath, node) {
|
||||
assert.ok(node.id, `expected identifer in ${loc(filepath, node)}`);
|
||||
assert.strictEqual(node.id.type, 'Identifier', `expected identifer in ${loc(filepath, node)}`);
|
||||
return node.id.name;
|
||||
}
|
||||
|
||||
function handleExportNamedDeclaration(filepath, node) {
|
||||
if (!(filepath in exportMapLookup)) {
|
||||
exportMapLookup[filepath] = {};
|
||||
}
|
||||
const exportMap = exportMapLookup[filepath];
|
||||
|
||||
const declaration = node.declaration;
|
||||
if (declaration) {
|
||||
// `export class Foo{}` or `export function foo() {}`
|
||||
if (declaration.type === 'ClassDeclaration' || declaration.type === 'FunctionDeclaration') {
|
||||
const name = nameFromChildIdentifier(filepath, declaration);
|
||||
exportMap[name] = name;
|
||||
return;
|
||||
}
|
||||
|
||||
// `export const foo = 'bar', bam = 42`
|
||||
if (declaration.type === 'VariableDeclaration') {
|
||||
const declarations = declaration.declarations;
|
||||
assert.ok(declarations.length > 0, `expected variable declarations in ${loc(filepath, declaration)}`);
|
||||
for (const declarator of declarations) {
|
||||
assert.strictEqual(declarator.type, 'VariableDeclarator', `unexpected "${declarator.type}" in ${loc(filepath, declarator)}`);
|
||||
const name = nameFromChildIdentifier(filepath, declarator);
|
||||
exportMap[name] = name;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected named export "${declaration.type}" in ${loc(filepath, declaration)}`);
|
||||
}
|
||||
|
||||
let prefix = '';
|
||||
const source = node.source;
|
||||
if (source) {
|
||||
// `export foo from 'bar'`
|
||||
assert.strictEqual(source.type, 'Literal', `unexpected export source "${source.type}" in ${loc(filepath, source)}`);
|
||||
prefix = `${source.value} `;
|
||||
}
|
||||
|
||||
const specifiers = node.specifiers;
|
||||
assert.ok(specifiers.length > 0, `expected export specifiers in ${loc(filepath, node)}`);
|
||||
// `export {foo, bar}` or `export {default as Foo} from 'bar'`
|
||||
for (const specifier of specifiers) {
|
||||
assert.strictEqual(specifier.type, 'ExportSpecifier', `unexpected export specifier in ${loc(filepath, specifier)}`);
|
||||
|
||||
const local = specifier.local;
|
||||
assert.strictEqual(local.type, 'Identifier', `unexpected local specifier "${local.type} in ${loc(filepath, local)}`);
|
||||
|
||||
const exported = specifier.exported;
|
||||
assert.strictEqual(local.type, 'Identifier', `unexpected exported specifier "${exported.type} in ${loc(filepath, exported)}`);
|
||||
|
||||
exportMap[prefix + local.name] = exported.name;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDefaultDeclaration(filepath, node) {
|
||||
if (!(filepath in exportMapLookup)) {
|
||||
exportMapLookup[filepath] = {};
|
||||
}
|
||||
const exportMap = exportMapLookup[filepath];
|
||||
|
||||
const declaration = node.declaration;
|
||||
if (declaration) {
|
||||
// `export default class Foo{}` or `export default function foo () {}`
|
||||
if (declaration.type === 'ClassDeclaration' || declaration.type === 'FunctionDeclaration') {
|
||||
const name = nameFromChildIdentifier(filepath, declaration);
|
||||
exportMap[name] = 'default';
|
||||
return;
|
||||
}
|
||||
|
||||
// `export default foo`
|
||||
if (declaration.type === 'Identifier') {
|
||||
exportMap[declaration.name] = 'default';
|
||||
return;
|
||||
}
|
||||
|
||||
// `export default {foo: 'bar'}` or `export default 42`
|
||||
if (declaration.type === 'ObjectExpression' || declaration.type === 'Literal') {
|
||||
exportMap[''] = 'default';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected default export "${declaration.type}" in ${loc(filepath, declaration)}`);
|
||||
}
|
||||
|
||||
exports.astNodeVisitor = {
|
||||
visitNode: (node, event, parser, filepath) => {
|
||||
if (node.type === 'ExportNamedDeclaration') {
|
||||
return handleExportNamedDeclaration(filepath, node);
|
||||
}
|
||||
|
||||
if (node.type === 'ExportDefaultDeclaration') {
|
||||
return handleDefaultDeclaration(filepath, node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const moduleLookup = {};
|
||||
|
||||
exports.handlers = {
|
||||
|
||||
// create a lookup of @module doclets
|
||||
newDoclet: event => {
|
||||
const doclet = event.doclet;
|
||||
if (doclet.kind === 'module') {
|
||||
const filepath = path.join(doclet.meta.path, doclet.meta.filename);
|
||||
|
||||
assert.ok(!(filepath in moduleLookup), `duplicate @module doc in ${filepath}`);
|
||||
moduleLookup[filepath] = doclet;
|
||||
}
|
||||
},
|
||||
|
||||
// assign the `exportMap` property to @module doclets
|
||||
parseComplete: event => {
|
||||
for (const filepath in moduleLookup) {
|
||||
assert.ok(filepath in exportMapLookup, `missing ${filepath} in export map lookup`);
|
||||
moduleLookup[filepath].exportMap = exportMapLookup[filepath];
|
||||
}
|
||||
|
||||
// make sure there was a @module doclet for each export map
|
||||
for (const filepath in exportMapLookup) {
|
||||
assert.ok(filepath in moduleLookup, `missing @module doclet in ${filepath}`);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user