Skip to content

Commit 49347c8

Browse files
authored
Arv document exported (#502)
* Add a flag to document all exported bindings This adds a boolean flag called `document-exported` (defaults to false) that effectively adds an empty comment to all exported bindings that do not already have a JSDoc comment. It also does the same for the members of exported classes and objects. ```js export class C { method() {} } ``` Both `C` and `C#method` are now part of the generated documentation. Related to #424 * Fix lint error * Rebase and use options pragma in test file * Create extractor type as a generalized comment/export getter * Document exported extractor
1 parent 662c2f9 commit 49347c8

File tree

13 files changed

+1142
-105
lines changed

13 files changed

+1142
-105
lines changed

docs/USAGE.md

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,41 @@ Commands:
1919
readme inject documentation into your README.md
2020

2121
Options:
22-
--help Show help [boolean]
23-
--version Show version number [boolean]
24-
--shallow shallow mode turns off dependency resolution, only
25-
processing the specified files (or the main script
26-
specified in package.json) [boolean] [default: false]
27-
--config, -c configuration file. an array defining explicit sort order
28-
--external a string / glob match pattern that defines which external
29-
modules will be whitelisted and included in the generated
30-
documentation. [default: null]
31-
--extension, -e only input source files matching this extension will be
32-
parsed, this option can be used multiple times.
33-
--polyglot polyglot mode turns off dependency resolution and enables
34-
multi-language support. use this to document c++ [boolean]
35-
--private, -p generate documentation tagged as private
22+
--help Show help [boolean]
23+
--version Show version number [boolean]
24+
--shallow shallow mode turns off dependency resolution, only
25+
processing the specified files (or the main script
26+
specified in package.json) [boolean] [default: false]
27+
--config, -c configuration file. an array defining explicit sort order
28+
--external a string / glob match pattern that defines which external
29+
modules will be whitelisted and included in the generated
30+
documentation. [default: null]
31+
--extension, -e only input source files matching this extension will be
32+
parsed, this option can be used multiple times.
33+
--polyglot polyglot mode turns off dependency resolution and enables
34+
multi-language support. use this to document c++[boolean]
35+
--private, -p generate documentation tagged as private
3636
[boolean] [default: false]
37-
--access, -a Include only comments with a given access level, out of
38-
private, protected, public, undefined. By default, public,
39-
protected, and undefined access levels are included
37+
--access, -a Include only comments with a given access level, out of
38+
private, protected, public, undefined. By default,
39+
public, protected, and undefined access levels are
40+
included
4041
[choices: "public", "private", "protected", "undefined"]
41-
--github, -g infer links to github in documentation [boolean]
42-
--infer-private Infer private access based on the name. This is a regular
43-
expression that is used to match the name [string]
44-
--theme, -t specify a theme: this must be a valid theme module
45-
--name project name. by default, inferred from package.json
46-
--watch, -w watch input files and rebuild documentation when they
47-
change [boolean]
48-
--project-version project version. by default, inferred from package.json
49-
--output, -o output location. omit for stdout, otherwise is a filename
50-
for single-file outputs and a directory name for multi-file
51-
outputs like html [default: "stdout"]
52-
--format, -f [choices: "json", "md", "html"] [default: "json"]
42+
--github, -g infer links to github in documentation [boolean]
43+
--infer-private Infer private access based on the name. This is a regular
44+
expression that is used to match the name [string]
45+
--document-exported Generate documentation for all exported bindings and
46+
members even if there is no JSDoc for them
47+
[boolean] [default: false]
48+
--theme, -t specify a theme: this must be a valid theme module
49+
--name project name. by default, inferred from package.json
50+
--watch, -w watch input files and rebuild documentation when they
51+
change [boolean]
52+
--project-version project version. by default, inferred from package.json
53+
--output, -o output location. omit for stdout, otherwise is a filename
54+
for single-file outputs and a directory name for
55+
multi-file outputs like html [default: "stdout"]
56+
--format, -f [choices: "json", "md", "remark", "html"] [default: "json"]
5357

5458
Examples:
5559
documentation build foo.js -f md > parse documentation in a file and

index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ function buildSync(indexes, options) {
194194
return [];
195195
}
196196

197-
return parseFn(indexObject).map(buildPipeline);
197+
return parseFn(indexObject, options).map(buildPipeline);
198198
})
199199
.filter(Boolean), options)));
200200
}
@@ -249,7 +249,7 @@ function lint(indexes, options, callback) {
249249
inputs
250250
.filter(filterJS(options.extension, options.polyglot))
251251
.reduce(function (memo, file) {
252-
return memo.concat(parseFn(file).map(lintPipeline));
252+
return memo.concat(parseFn(file, options).map(lintPipeline));
253253
}, [])
254254
.filter(Boolean))));
255255
});

lib/commands/shared_options.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ function sharedInputOptions(parser) {
5353
type: 'string',
5454
describe: 'Infer private access based on the name. This is a regular expression that ' +
5555
'is used to match the name'
56+
})
57+
.option('document-exported', {
58+
type: 'boolean',
59+
describe: 'Generate documentation for all exported bindings and members ' +
60+
'even if there is no JSDoc for them',
61+
default: false
5662
});
5763
}
5864

lib/extractors/comments.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
var traverse = require('babel-traverse').default,
2+
isJSDocComment = require('../../lib/is_jsdoc_comment');
3+
4+
/**
5+
* Iterate through the abstract syntax tree, finding a different kind of comment
6+
* each time, and optionally including context. This is how we find
7+
* JSDoc annotations that will become part of documentation
8+
* @param {string} type comment type to find
9+
* @param {boolean} includeContext to include context in the nodes
10+
* @param {Object} ast the babel-parsed syntax tree
11+
* @param {Function} addComment a method that creates a new comment if necessary
12+
* @returns {Array<Object>} comments
13+
* @private
14+
*/
15+
function walkComments(type, includeContext, ast, addComment) {
16+
var newResults = [];
17+
18+
traverse(ast, {
19+
/**
20+
* Process a parse in an abstract syntax tree
21+
* @param {Object} path ast path
22+
* @returns {undefined} causes side effects
23+
* @private
24+
*/
25+
enter: function (path) {
26+
/**
27+
* Parse a comment with doctrine and decorate the result with file position and code context.
28+
*
29+
* @param {Object} comment the current state of the parsed JSDoc comment
30+
* @return {undefined} this emits data
31+
*/
32+
function parseComment(comment) {
33+
newResults.push(addComment(comment.value, comment.loc, path, path.node.loc, includeContext));
34+
}
35+
36+
(path.node[type] || [])
37+
.filter(isJSDocComment)
38+
.forEach(parseComment);
39+
}
40+
});
41+
42+
traverse.clearCache();
43+
44+
return newResults;
45+
}
46+
47+
module.exports = walkComments;

lib/extractors/exported.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var traverse = require('babel-traverse').default,
2+
isJSDocComment = require('../../lib/is_jsdoc_comment');
3+
4+
5+
/**
6+
* Iterate through the abstract syntax tree, finding ES6-style exports,
7+
* and inserting blank comments into documentation.js's processing stream.
8+
* Through inference steps, these comments gain more information and are automatically
9+
* documented as well as we can.
10+
* @param {Object} ast the babel-parsed syntax tree
11+
* @param {Function} addComment a method that creates a new comment if necessary
12+
* @returns {Array<Object>} comments
13+
* @private
14+
*/
15+
function walkExported(ast, addComment) {
16+
var newResults = [];
17+
18+
function addBlankComment(path, node) {
19+
return addComment('', node.loc, path, node.loc, true);
20+
}
21+
22+
traverse(ast, {
23+
enter: function (path) {
24+
if (path.isExportDeclaration()) {
25+
if (!hasJSDocComment(path)) {
26+
if (!path.node.declaration) {
27+
return;
28+
}
29+
const node = path.node.declaration;
30+
newResults.push(addBlankComment(path, node));
31+
}
32+
} else if ((path.isClassProperty() || path.isClassMethod()) &&
33+
!hasJSDocComment(path) && inExportedClass(path)) {
34+
newResults.push(addBlankComment(path, path.node));
35+
} else if ((path.isObjectProperty() || path.isObjectMethod()) &&
36+
!hasJSDocComment(path) && inExportedObject(path)) {
37+
newResults.push(addBlankComment(path, path.node));
38+
}
39+
}
40+
});
41+
42+
return newResults;
43+
}
44+
45+
function hasJSDocComment(path) {
46+
return path.node.leadingComments && path.node.leadingComments.some(isJSDocComment);
47+
}
48+
49+
function inExportedClass(path) {
50+
var c = path.parentPath.parentPath;
51+
return c.isClass() && c.parentPath.isExportDeclaration();
52+
}
53+
54+
function inExportedObject(path) {
55+
// ObjectExpression -> VariableDeclarator -> VariableDeclaration -> ExportNamedDeclaration
56+
var p = path.parentPath.parentPath;
57+
if (!p.isVariableDeclarator()) {
58+
return false;
59+
}
60+
return p.parentPath.parentPath.isExportDeclaration();
61+
}
62+
63+
module.exports = walkExported;

lib/parsers/javascript.js

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
'use strict';
22

33
var babylon = require('babylon'),
4-
traverse = require('babel-traverse').default,
54
extend = require('extend'),
6-
isJSDocComment = require('../../lib/is_jsdoc_comment'),
7-
parse = require('../../lib/parse');
5+
_ = require('lodash'),
6+
parse = require('../../lib/parse'),
7+
walkComments = require('../extractors/comments'),
8+
walkExported = require('../extractors/exported');
89

910
/**
1011
* Left-pad a string so that it can be sorted lexicographically. We sort
@@ -27,10 +28,13 @@ function leftPad(str, width) {
2728
* reads the file, parses the JavaScript, and parses the JSDoc.
2829
*
2930
* @param {Object} data a chunk of data provided by module-deps
31+
* @param {Object} options options
3032
* @return {Array<Object>} an array of parsed comments
3133
*/
32-
function parseJavaScript(data) {
33-
var results = [];
34+
function parseJavaScript(data, options) {
35+
options = options || {};
36+
var visited = {};
37+
3438
var ast = babylon.parse(data.source, {
3539
allowImportExportEverywhere: true,
3640
sourceType: 'module',
@@ -52,75 +56,44 @@ function parseJavaScript(data) {
5256
]
5357
});
5458

55-
var visited = {};
59+
var addComment = _addComment.bind(null, visited, data);
5660

57-
/**
58-
* Iterate through the abstract syntax tree, finding a different kind of comment
59-
* each time, and optionally including context. This is how we find
60-
* JSDoc annotations that will become part of documentation
61-
* @param {Object} ast the babel-parsed syntax tree
62-
* @param {string} type comment type to find
63-
* @param {boolean} includeContext to include context in the nodes
64-
* @returns {Array<Object>} comments
65-
* @private
66-
*/
67-
function walkComments(ast, type, includeContext) {
68-
traverse(ast, {
69-
/**
70-
* Process a parse in an abstract syntax tree
71-
* @param {Object} path ast path
72-
* @returns {undefined} causes side effects
73-
* @private
74-
*/
75-
enter: function (path) {
76-
/**
77-
* Parse a comment with doctrine and decorate the result with file position and code context.
78-
*
79-
* @param {Object} comment the current state of the parsed JSDoc comment
80-
* @return {undefined} this emits data
81-
*/
82-
function parseComment(comment) {
83-
var context = {
84-
loc: extend({}, JSON.parse(JSON.stringify(path.node.loc))),
85-
file: data.file,
86-
sortKey: data.sortKey + ' ' + leftPad(path.node.loc.start.line, 8)
87-
};
88-
// Avoid visiting the same comment twice as a leading
89-
// and trailing node
90-
var key = JSON.stringify(comment.loc);
91-
if (!visited[key]) {
92-
visited[key] = true;
93-
if (includeContext) {
94-
// This is non-enumerable so that it doesn't get stringified in
95-
// output; e.g. by the documentation binary.
96-
Object.defineProperty(context, 'ast', {
97-
enumerable: false,
98-
value: path
99-
});
61+
return _.flatMap([
62+
walkComments.bind(null, 'leadingComments', true),
63+
walkComments.bind(null, 'innerComments', false),
64+
walkComments.bind(null, 'trailingComments', false),
65+
options.documentExported && walkExported
66+
].filter(Boolean), function (fn) {
67+
return fn(ast, addComment);
68+
}).filter(Boolean);
69+
}
10070

101-
if (path.parentPath && path.parentPath.node) {
102-
context.code = data.source.substring
103-
.apply(data.source, path.parentPath.node.range);
104-
}
105-
}
106-
results.push(parse(comment.value, comment.loc, context));
107-
}
108-
}
71+
function _addComment(visited, data, commentValue, commentLoc, path, nodeLoc, includeContext) {
72+
var context = {
73+
loc: extend({}, JSON.parse(JSON.stringify(nodeLoc))),
74+
file: data.file,
75+
sortKey: data.sortKey + ' ' + leftPad(nodeLoc.start.line, 8)
76+
};
77+
// Avoid visiting the same comment twice as a leading
78+
// and trailing node
79+
var key = JSON.stringify(commentLoc);
80+
if (!visited[key]) {
81+
visited[key] = true;
82+
if (includeContext) {
83+
// This is non-enumerable so that it doesn't get stringified in
84+
// output; e.g. by the documentation binary.
85+
Object.defineProperty(context, 'ast', {
86+
enumerable: false,
87+
value: path
88+
});
10989

110-
(path.node[type] || [])
111-
.filter(isJSDocComment)
112-
.forEach(parseComment);
90+
if (path.parentPath && path.parentPath.node) {
91+
context.code = data.source.substring
92+
.apply(data.source, path.parentPath.node.range);
11393
}
114-
});
115-
116-
traverse.clearCache();
94+
}
95+
return parse(commentValue, commentLoc, context);
11796
}
118-
119-
walkComments(ast, 'leadingComments', true);
120-
walkComments(ast, 'innerComments', false);
121-
walkComments(ast, 'trailingComments', false);
122-
123-
return results;
12497
}
12598

12699
module.exports = parseJavaScript;

test/bin.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,19 @@ test('fatal error', options, function (t) {
341341
t.end();
342342
}, false);
343343
});
344+
345+
test('build --document-exported', function (t) {
346+
347+
documentation(['build fixture/document-exported.input.js --document-exported -f md'], {}, function (err, data) {
348+
t.error(err);
349+
350+
var outputfile = path.join(__dirname, 'fixture', 'document-exported.output.md');
351+
if (process.env.UPDATE) {
352+
fs.writeFileSync(outputfile, data, 'utf8');
353+
}
354+
355+
var expect = fs.readFileSync(outputfile, 'utf-8');
356+
t.equal(data, expect);
357+
t.end();
358+
}, false);
359+
}, options);

0 commit comments

Comments
 (0)