documentation/lib/nest.js
Tom MacWright 73747306a0 refactor(nest): Better nesting implementation (#732)
* refactor(nest): Better nesting implementation

This nesting implementation uses a proper recursive tree algorithm

Fixes https://github.com/documentationjs/documentation/issues/554

BREAKING CHANGE: referencing inferred destructure params without
renaming them, like $0.x, from JSDoc comments will no longer
work. To reference them, instead add a param tag to name the
destructuring param, and then refer to members of that name.

Before:

```js
/**
 * @param {number} $0.x a member of x
 */
function a({ x }) {}
```

After:

```js
/**
 * @param {Object} options
 * @param {number} options.x a member of x
 */
function a({ x }) {}
```

* Address review comments

* Reduce testing node requirement back down to 4

* Don't output empty properties, reduce diff noise

* Rearrange and document params

* Simplify param inference, update test fixtures. This is focused around Array destructuring: documenting destructured array elements with indices instead of names, because the names are purely internal details

* Use temporary fork to get through blocker
2017-04-21 17:28:53 -04:00

105 lines
3.0 KiB
JavaScript

/* @flow */
'use strict';
var _ = require('lodash');
const PATH_SPLIT = /(?:\[])?\./g;
function removeUnnamedTags(
tags /*: Array<CommentTag> */
) /*: Array<CommentTagNamed> */ {
return tags.filter(tag => typeof tag.name === 'string');
}
var 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 {Object} comment a comment with tags
* @param {string} tagTitle the tag to nest
* @param {string} target the tag to nest into
* @returns {Object} nested comment
*/
var nestTag = (
tags /*: Array<CommentTag> */
// 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) {
_.assign(node, {
properties: (node.properties || []).concat(tag)
});
} else {
// The recursive case: try to find the child that owns
// this tag.
let child = node.properties &&
node.properties.find(
property => parts[0] === _.last(property.name.split(PATH_SPLIT))
);
if (!child) {
if (tag.name.match(/^(\$\d+)/)) {
throw new Error(
`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`
);
}
throw new Error(`Parent of ${tag.name} not found`);
}
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
* @return {Object} nested comment
*/
var nest = (comment /*: Comment*/) =>
_.assign(comment, {
params: nestTag(comment.params),
properties: nestTag(comment.properties)
});
module.exports = nest;
module.exports.nestTag = nestTag;