1 | const _ = require('lodash');
|
2 |
|
3 | const PATH_SPLIT = /(?:\[])?\./g;
|
4 |
|
5 | function removeUnnamedTags(tags) {
|
6 | return tags.filter(tag => typeof tag.name === 'string');
|
7 | }
|
8 |
|
9 | const 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 | */
|
33 | const 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 | */
|
100 | const nest = comment =>
|
101 | Object.assign(comment, {
|
102 | params: nestTag(comment.params, comment.errors),
|
103 | properties: nestTag(comment.properties, comment.errors)
|
104 | });
|
105 |
|
106 | module.exports = nest;
|
107 | module.exports.nestTag = nestTag;
|