documentation/src/nest.js

108 lines
3.1 KiB
JavaScript

const _ = require('lodash');
const PATH_SPLIT = /(?:\[])?\./g;
function removeUnnamedTags(tags) {
return tags.filter(tag => typeof tag.name === 'string');
}
const tagDepth = tag => tag.name.split(PATH_SPLIT).length;
/**
* Nest nestable tags, like param and property, into nested
* arrays that are suitable for output.
* Okay, so we're building a tree of comments, with the tag.name
* being the indexer. We sort by depth, so that we add each
* level of the tree incrementally, and throw if we run against
* a node that doesn't have a parent.
*
* foo.abe
* foo.bar.baz
* foo.bar.a
* foo.bar[].bax
*
* foo -> .abe
* \-> .bar -> .baz
* \-> .a
* \-> [].baz
*
* @private
* @param {Array<CommentTag>} tags a list of tags
* @returns {Object} nested comment
*/
const nestTag = (
tags,
errors
// Use lodash here both for brevity and also because, unlike JavaScript's
// sort, it's stable.
) =>
_.sortBy(removeUnnamedTags(tags), tagDepth).reduce(
(memo, tag) => {
function insertTag(node, parts) {
// The 'done' case: we're at parts.length === 1,
// this is where the node should be placed. We reliably
// get to this case because the recursive method
// is always passed parts.slice(1)
if (parts.length === 1) {
Object.assign(node, {
properties: (node.properties || []).concat(tag)
});
} else {
// The recursive case: try to find the child that owns
// this tag.
const child =
node.properties &&
node.properties.find(
property => parts[0] === _.last(property.name.split(PATH_SPLIT))
);
if (!child) {
if (tag.name.match(/^(\$\d+)/)) {
errors.push({
message:
`Parent of ${
tag.name
} not found. To document a destructuring\n` +
`type, add a @param tag in its position to specify the name of the\n` +
`destructured parameter`,
commentLineNumber: tag.lineNumber
});
} else {
errors.push({
message: `Parent of ${tag.name} not found`,
commentLineNumber: tag.lineNumber
});
}
} else {
insertTag(child, parts.slice(1));
}
}
}
insertTag(memo, tag.name.split(PATH_SPLIT));
return memo;
},
{ properties: [] }
).properties;
/**
* Nests
* [parameters with properties](http://usejsdoc.org/tags-param.html#parameters-with-properties).
*
* A parameter `employee.name` will be attached to the parent parameter `employee` in
* a `properties` array.
*
* This assumes that incoming comments have been flattened.
*
* @param {Object} comment input comment
* @returns {Object} nested comment
*/
const nest = comment =>
Object.assign(comment, {
params: nestTag(comment.params, comment.errors),
properties: nestTag(comment.properties, comment.errors)
});
module.exports = nest;
module.exports.nestTag = nestTag;