UNPKG

3.16 kBJavaScriptView Raw
1const _ = require('lodash');
2
3const PATH_SPLIT = /(?:\[])?\./g;
4
5function removeUnnamedTags(tags) {
6 return tags.filter(tag => typeof tag.name === 'string');
7}
8
9const tagDepth = tag => tag.name.split(PATH_SPLIT).length;
10
11/**
12 * Nest nestable tags, like param and property, into nested
13 * arrays that are suitable for output.
14 * Okay, so we're building a tree of comments, with the tag.name
15 * being the indexer. We sort by depth, so that we add each
16 * level of the tree incrementally, and throw if we run against
17 * a node that doesn't have a parent.
18 *
19 * foo.abe
20 * foo.bar.baz
21 * foo.bar.a
22 * foo.bar[].bax
23 *
24 * foo -> .abe
25 * \-> .bar -> .baz
26 * \-> .a
27 * \-> [].baz
28 *
29 * @private
30 * @param {Array<CommentTag>} tags a list of tags
31 * @returns {Object} nested comment
32 */
33const nestTag = (
34 tags,
35 errors
36 // Use lodash here both for brevity and also because, unlike JavaScript's
37 // sort, it's stable.
38) =>
39 _.sortBy(removeUnnamedTags(tags), tagDepth).reduce(
40 (memo, tag) => {
41 function insertTag(node, parts) {
42 // The 'done' case: we're at parts.length === 1,
43 // this is where the node should be placed. We reliably
44 // get to this case because the recursive method
45 // is always passed parts.slice(1)
46 if (parts.length === 1) {
47 Object.assign(node, {
48 properties: (node.properties || []).concat(tag)
49 });
50 } else {
51 // The recursive case: try to find the child that owns
52 // this tag.
53 const child =
54 node.properties &&
55 node.properties.find(
56 property => parts[0] === _.last(property.name.split(PATH_SPLIT))
57 );
58
59 if (!child) {
60 if (tag.name.match(/^(\$\d+)/)) {
61 errors.push({
62 message:
63 `Parent of ${
64 tag.name
65 } not found. To document a destructuring\n` +
66 `type, add a @param tag in its position to specify the name of the\n` +
67 `destructured parameter`,
68 commentLineNumber: tag.lineNumber
69 });
70 } else {
71 errors.push({
72 message: `Parent of ${tag.name} not found`,
73 commentLineNumber: tag.lineNumber
74 });
75 }
76 } else {
77 insertTag(child, parts.slice(1));
78 }
79 }
80 }
81
82 insertTag(memo, tag.name.split(PATH_SPLIT));
83 return memo;
84 },
85 { properties: [] }
86 ).properties;
87
88/**
89 * Nests
90 * [parameters with properties](http://usejsdoc.org/tags-param.html#parameters-with-properties).
91 *
92 * A parameter `employee.name` will be attached to the parent parameter `employee` in
93 * a `properties` array.
94 *
95 * This assumes that incoming comments have been flattened.
96 *
97 * @param {Object} comment input comment
98 * @returns {Object} nested comment
99 */
100const nest = comment =>
101 Object.assign(comment, {
102 params: nestTag(comment.params, comment.errors),
103 properties: nestTag(comment.properties, comment.errors)
104 });
105
106module.exports = nest;
107module.exports.nestTag = nestTag;