171 lines
5.5 KiB
JavaScript
171 lines
5.5 KiB
JavaScript
/**
|
|
* 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}`);
|
|
}
|
|
}
|
|
|
|
};
|