Skip to content

Commit c94412a

Browse files
rhendrictmcw
authored andcommitted
feat: groups in toc (#895)
1 parent 7a07d51 commit c94412a

File tree

10 files changed

+290
-45
lines changed

10 files changed

+290
-45
lines changed

__tests__/__snapshots__/test.js.snap

+49
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,55 @@ World
3434
"
3535
`;
3636

37+
exports[`config with nested sections 1`] = `
38+
"<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
39+
40+
## Alpha
41+
42+
43+
44+
45+
### third
46+
47+
This function is third
48+
49+
### first
50+
51+
This function is first
52+
53+
## Bravo
54+
55+
Contains a subsection!
56+
57+
58+
### Charlie
59+
60+
Second is in here
61+
62+
63+
#### second
64+
65+
This class has some members
66+
67+
##### foo
68+
69+
second::foo
70+
71+
**Parameters**
72+
73+
- \`pork\`
74+
75+
##### bar
76+
77+
second::bar
78+
79+
**Parameters**
80+
81+
- \`beans\`
82+
- \`rice\`
83+
"
84+
`;
85+
3786
exports[`external modules option 1`] = `
3887
Array [
3988
Object {

__tests__/fixture/sections.config.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
toc:
2+
- name: Alpha
3+
children:
4+
- third
5+
- first
6+
- name: Bravo
7+
description: Contains a subsection!
8+
children:
9+
- name: Charlie
10+
description: Second is in here
11+
children:
12+
- second

__tests__/fixture/sections.input.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* This function is first
3+
*/
4+
function first() {}
5+
6+
/**
7+
* This class has some members
8+
*/
9+
function second() {}
10+
11+
/**
12+
* second::foo
13+
*/
14+
second.prototype.foo = function(pork) {};
15+
16+
/**
17+
* second::bar
18+
*/
19+
second.prototype.bar = function(beans, rice) {};
20+
21+
/**
22+
* This function is third
23+
*/
24+
function third() {}

__tests__/lib/__snapshots__/sort.js.snap

+65-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,67 @@
33
exports[`sort toc with files 1`] = `
44
Array [
55
Object {
6-
"file": "test/fixture/snowflake.md",
6+
"description": Object {
7+
"children": Array [
8+
Object {
9+
"children": Array [
10+
Object {
11+
"position": Position {
12+
"end": Object {
13+
"column": 16,
14+
"line": 1,
15+
"offset": 15,
16+
},
17+
"indent": Array [],
18+
"start": Object {
19+
"column": 3,
20+
"line": 1,
21+
"offset": 2,
22+
},
23+
},
24+
"type": "text",
25+
"value": "The Snowflake",
26+
},
27+
],
28+
"depth": 1,
29+
"position": Position {
30+
"end": Object {
31+
"column": 16,
32+
"line": 1,
33+
"offset": 15,
34+
},
35+
"indent": Array [],
36+
"start": Object {
37+
"column": 1,
38+
"line": 1,
39+
"offset": 0,
40+
},
41+
},
42+
"type": "heading",
43+
},
44+
],
45+
"position": Object {
46+
"end": Object {
47+
"column": 1,
48+
"line": 2,
49+
"offset": 16,
50+
},
51+
"start": Object {
52+
"column": 1,
53+
"line": 1,
54+
"offset": 0,
55+
},
56+
},
57+
"type": "root",
58+
},
759
"kind": "note",
860
"name": "snowflake",
61+
"path": Array [
62+
Object {
63+
"name": "snowflake",
64+
"scope": "static",
65+
},
66+
],
967
},
1068
Object {
1169
"context": Object {
@@ -86,6 +144,12 @@ Array [
86144
},
87145
"kind": "note",
88146
"name": "snowflake",
147+
"path": Array [
148+
Object {
149+
"name": "snowflake",
150+
"scope": "static",
151+
},
152+
],
89153
},
90154
Object {
91155
"context": Object {

__tests__/lib/sort.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ test('sort stream with configuration and a section', function() {
8787
}
8888
}
8989
},
90-
kind: 'note'
90+
kind: 'note',
91+
path: [
92+
{
93+
name: 'This is the banana type',
94+
scope: 'static'
95+
}
96+
]
9197
};
9298

9399
expect(
@@ -161,7 +167,13 @@ test('sort an already-sorted stream containing a section/description', function(
161167
}
162168
}
163169
},
164-
kind: 'note'
170+
kind: 'note',
171+
path: [
172+
{
173+
name: 'This is the banana type',
174+
scope: 'static'
175+
}
176+
]
165177
};
166178

167179
var config = {
@@ -180,7 +192,7 @@ test('sort toc with files', function() {
180192

181193
var snowflake = {
182194
name: 'snowflake',
183-
file: 'test/fixture/snowflake.md'
195+
file: path.join(__dirname, '../fixture/snowflake.md')
184196
};
185197

186198
expect(

__tests__/test.js

+9
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,15 @@ test('config', async function() {
179179
expect(md).toMatchSnapshot();
180180
});
181181

182+
test('config with nested sections', async function() {
183+
var file = path.join(__dirname, 'fixture', 'sections.input.js');
184+
const out = await documentation.build([file], {
185+
config: path.join(__dirname, 'fixture', 'sections.config.yml')
186+
});
187+
const md = await outputMarkdown(out, {});
188+
expect(md).toMatchSnapshot();
189+
});
190+
182191
test('multi-file input', async function() {
183192
const result = await documentation.build(
184193
[

docs/CONFIG.md

+19
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,22 @@ and areas on the sphere.
5353
```
5454

5555
it would produce the same output as the previous example.
56+
57+
## Groups
58+
59+
The `children` property can be used to group content under headings instead of just arranging them in order. Example:
60+
61+
```yml
62+
toc:
63+
- name: Geography
64+
children:
65+
- Map
66+
- LngLat
67+
- LngLatBounds
68+
- name: Navigation
69+
description: |
70+
Here are some helper functions for navigation.
71+
children:
72+
- shortestPath
73+
- salesman
74+
```

src/hierarchy.js

+54-17
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,73 @@ module.exports = function(comments) {
5555
members: getMembers()
5656
};
5757

58+
const namesToUnroot = [];
59+
5860
comments.forEach(comment => {
59-
var path = [];
61+
let path = comment.path;
62+
if (!path) {
63+
path = [];
64+
65+
if (comment.memberof) {
66+
// TODO: full namepath parsing
67+
path = comment.memberof
68+
.split('.')
69+
.map(segment => ({ scope: 'static', name: segment }));
70+
}
6071

61-
if (comment.memberof) {
62-
// TODO: full namepath parsing
63-
path = comment.memberof.split('.').map(segment => ['static', segment]);
64-
}
72+
if (!comment.name) {
73+
comment.errors.push({
74+
message: 'could not determine @name for hierarchy'
75+
});
76+
}
6577

66-
if (!comment.name) {
67-
comment.errors.push({
68-
message: 'could not determine @name for hierarchy'
78+
path.push({
79+
scope: comment.scope || 'static',
80+
name: comment.name || 'unknown_' + id++
6981
});
7082
}
7183

72-
path.push([comment.scope || 'static', comment.name || 'unknown_' + id++]);
73-
7484
var node = root;
7585

7686
while (path.length) {
77-
var segment = path.shift(), scope = segment[0], name = segment[1];
87+
var segment = path.shift(),
88+
scope = segment.scope,
89+
name = segment.name;
7890

7991
if (!hasOwnProperty.call(node.members[scope], name)) {
80-
node.members[scope][name] = {
81-
comments: [],
82-
members: getMembers()
83-
};
92+
// If segment.toc is true, everything up to this point in the path
93+
// represents how the documentation should be nested, but not how the
94+
// actual code is nested. To ensure that child members end up in the
95+
// right places in the tree, we temporarily push the same node a second
96+
// time to the root of the tree, and unroot it after all the comments
97+
// have found their homes.
98+
if (
99+
segment.toc &&
100+
node !== root &&
101+
hasOwnProperty.call(root.members[scope], name)
102+
) {
103+
node.members[scope][name] = root.members[scope][name];
104+
namesToUnroot.push(name);
105+
} else {
106+
const newNode = (node.members[scope][name] = {
107+
comments: [],
108+
members: getMembers()
109+
});
110+
if (segment.toc && node !== root) {
111+
root.members[scope][name] = newNode;
112+
namesToUnroot.push(name);
113+
}
114+
}
84115
}
85116

86117
node = node.members[scope][name];
87118
}
88119

89120
node.comments.push(comment);
90121
});
122+
namesToUnroot.forEach(function(name) {
123+
delete root.members.static[name];
124+
});
91125

92126
/*
93127
* Massage the hierarchy into a format more suitable for downstream consumers:
@@ -107,7 +141,8 @@ module.exports = function(comments) {
107141
* Person~say // the inner method named "say."
108142
*/
109143
function toComments(nodes, root, hasUndefinedParent, path) {
110-
var result = [], scope;
144+
var result = [],
145+
scope;
111146

112147
path = path || [];
113148

@@ -119,7 +154,9 @@ module.exports = function(comments) {
119154
node.members[scope],
120155
root || result,
121156
!node.comments.length,
122-
node.comments.length ? path.concat(node.comments[0]) : []
157+
node.comments.length && node.comments[0].kind !== 'note'
158+
? path.concat(node.comments[0])
159+
: []
123160
);
124161
}
125162

src/output/markdown_ast.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,16 @@ function buildMarkdownAST(
303303
}
304304

305305
if (comment.kind === 'note') {
306-
return [u('heading', { depth }, [u('text', comment.name || '')])].concat(
307-
comment.description
308-
);
306+
return [u('heading', { depth }, [u('text', comment.name || '')])]
307+
.concat(comment.description)
308+
.concat(
309+
!!comment.members.static.length &&
310+
comment.members.static.reduce(
311+
(memo, child) => memo.concat(generate(depth + 1, child)),
312+
[]
313+
)
314+
)
315+
.filter(Boolean);
309316
}
310317

311318
return [u('heading', { depth }, [u('text', comment.name || '')])]

0 commit comments

Comments
 (0)