Skip to content

Commit ec49b11

Browse files
committed
Add --resolver option (closes #42)
`--resolver` either accepts the name of a built-in resolver (findExportedComponentDefinition, findAllComponentDefinitions) or a path to a module that exports a resolver function. With this option, react-docgen can be more easily customized. It is not necessary anymore to create a custom script and use the API just to use a different / custom resolver.
1 parent e76a4c6 commit ec49b11

File tree

7 files changed

+153
-23
lines changed

7 files changed

+153
-23
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
It uses [recast][] and [babylon][] to parse the source into an AST and provides methods to process this AST to extract the desired information. The output / return value is a JSON blob / JavaScript object.
66

7-
It provides a default implementation for React components defined via `React.createClass` or [ES2015 class definitions][classes]. These component definitions must follow certain guidelines in order to be analyzable (see below for more info).
7+
It provides a default implementation for React components defined via
8+
`React.createClass`, [ES2015 class definitions][classes] or functions
9+
(stateless components). These component definitions must follow certain
10+
guidelines in order to be analyzable (see below for more info).
811

912
## Install
1013

@@ -31,15 +34,22 @@ Options:
3134
--pretty pretty print JSON
3235
-x, --extension File extensions to consider. Repeat to define multiple extensions. Default: [js,jsx]
3336
-i, --ignore Folders to ignore. Default: [node_modules,__tests__]
37+
--resolver RESOLVER Resolver name (findAllComponentDefinitions, findExportedComponentDefinition) or
38+
path to a module that exports a resolver. [findExportedComponentDefinition]
3439
3540
Extract meta information from React components.
3641
If a directory is passed, it is recursively traversed.
3742
```
3843

39-
By default, `react-docgen` will look for the exported component created through `React.createClass` or a class definition in each file. Have a look below for how to customize this behavior.
44+
By default, `react-docgen` will look for the exported component created through
45+
`React.createClass`, a class definition or a function (stateless component) in
46+
each file. You can change that behavior with the `--resolver` option, which
47+
either expects the name of a built-in resolver or a path to JavaScript module
48+
exporting a resolver function. Have a look below for [more information about
49+
resolvers](#resolver).
4050

41-
Have a look at `example/` for an example of how to use the result to generate
42-
a markdown version of the documentation.
51+
Have a look at `example/` for an example of how to use the result to generate a
52+
markdown version of the documentation.
4353

4454
## API
4555

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
var React = require('react');
12+
13+
exports.ComponentA = React.createClass({
14+
displayName: 'ComponentA',
15+
render: function() {
16+
// ...
17+
},
18+
});
19+
20+
exports.ComponentB = React.createClass({
21+
displayName: 'ComponentB',
22+
render: function() {
23+
// ...
24+
},
25+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
/**
12+
* Dummy resolver that always returns the same AST node
13+
*/
14+
15+
const code = `
16+
({
17+
displayName: 'Custom',
18+
})
19+
`;
20+
21+
module.exports = function(ast, recast) {
22+
return (new recast.types.NodePath(recast.parse(code)))
23+
.get('program', 'body', 0, 'expression');
24+
};

bin/__tests__/react-docgen-test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,44 @@ describe('react-docgen CLI', () => {
204204
]);
205205
});
206206

207+
describe('--resolver', () => {
208+
pit('accepts the names of built in resolvers', () => {
209+
return Promise.all([
210+
// No option passed: same as --resolver=findExportedComponentDefinition
211+
run([
212+
path.join(__dirname, '../../example/components/Component.js'),
213+
]).then(([stdout]) => {
214+
expect(stdout).toContain('Component');
215+
}),
216+
217+
run([
218+
'--resolver=findExportedComponentDefinition',
219+
path.join(__dirname, '../../example/components/Component.js'),
220+
]).then(([stdout]) => {
221+
expect(stdout).toContain('Component');
222+
}),
223+
224+
run([
225+
'--resolver=findAllComponentDefinitions',
226+
path.join(__dirname, './example/MultipleComponents.js'),
227+
]).then(([stdout]) => {
228+
expect(stdout).toContain('ComponentA');
229+
expect(stdout).toContain('ComponentB');
230+
}),
231+
]);
232+
});
233+
234+
pit('accepts a path to a resolver function', () => {
235+
return Promise.all([
236+
run([
237+
'--resolver='+path.join(__dirname, './example/customResolver.js'),
238+
path.join(__dirname, '../../example/components/Component.js'),
239+
]).then(([stdout, stderr]) => {
240+
console.log(stderr);
241+
expect(stdout).toContain('Custom');
242+
}),
243+
]);
244+
});
245+
});
246+
207247
});

bin/react-docgen.js

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*
1010
*/
1111

12+
/*eslint no-process-exit: 0*/
13+
1214
var argv = require('nomnom')
1315
.script('react-docgen')
1416
.help(
@@ -20,46 +22,70 @@ var argv = require('nomnom')
2022
position: 0,
2123
help: 'A component file or directory. If no path is provided it reads from stdin.',
2224
metavar: 'PATH',
23-
list: true
25+
list: true,
2426
},
2527
out: {
2628
abbr: 'o',
2729
help: 'store extracted information in FILE',
28-
metavar: 'FILE'
30+
metavar: 'FILE',
2931
},
3032
pretty: {
3133
help: 'pretty print JSON',
32-
flag: true
34+
flag: true,
3335
},
3436
extension: {
3537
abbr: 'x',
3638
help: 'File extensions to consider. Repeat to define multiple extensions. Default:',
3739
list: true,
38-
default: ['js', 'jsx']
40+
default: ['js', 'jsx'],
3941
},
4042
ignoreDir: {
4143
abbr: 'i',
4244
full: 'ignore',
4345
help: 'Folders to ignore. Default:',
4446
list: true,
45-
default: ['node_modules', '__tests__']
46-
}
47+
default: ['node_modules', '__tests__'],
48+
},
49+
resolver: {
50+
help: 'Resolver name (findAllComponentDefinitions, findExportedComponentDefinition) or path to a module that exports a resolver.',
51+
metavar: 'RESOLVER',
52+
default: 'findExportedComponentDefinition',
53+
},
4754
})
4855
.parse();
4956

5057
var async = require('async');
5158
var dir = require('node-dir');
5259
var fs = require('fs');
5360
var parser = require('../dist/main.js');
61+
var path = require('path');
5462

5563
var output = argv.out;
5664
var paths = argv.path || [];
5765
var extensions = new RegExp('\\.(?:' + argv.extension.join('|') + ')$');
5866
var ignoreDir = argv.ignoreDir;
67+
var resolver;
68+
69+
if (argv.resolver) {
70+
switch(argv.resolver) {
71+
case 'findAllComponentDefinitions':
72+
resolver = require('../dist/resolver/findAllComponentDefinitions');
73+
break;
74+
case 'findExportedComponentDefinition':
75+
resolver = require('../dist/resolver/findExportedComponentDefinition');
76+
break;
77+
default: // treat value as module path
78+
resolver = require(path.resolve(process.cwd(), argv.resolver));
79+
}
80+
}
81+
82+
function parse(source) {
83+
return parser.parse(source, resolver);
84+
}
5985

60-
function writeError(msg, path) {
86+
function writeError(msg, filePath) {
6187
if (path) {
62-
process.stderr.write('Error with path "' + path + '": ');
88+
process.stderr.write('Error with path "' + filePath + '": ');
6389
}
6490
process.stderr.write(msg + '\n');
6591
if (msg instanceof Error) {
@@ -84,19 +110,19 @@ function exitWithResult(result) {
84110
process.exit(0);
85111
}
86112

87-
function traverseDir(path, result, done) {
113+
function traverseDir(filePath, result, done) {
88114
dir.readFiles(
89-
path,
115+
filePath,
90116
{
91117
match: extensions,
92-
excludeDir: ignoreDir
118+
excludeDir: ignoreDir,
93119
},
94120
function(error, content, filename, next) {
95121
if (error) {
96122
exitWithError(error);
97123
}
98124
try {
99-
result[filename] = parser.parse(content);
125+
result[filename] = parse(content);
100126
} catch(error) {
101127
writeError(error, filename);
102128
}
@@ -127,7 +153,7 @@ if (paths.length === 0) {
127153
});
128154
process.stdin.on('end', function () {
129155
try {
130-
exitWithResult(parser.parse(source));
156+
exitWithResult(parse(source));
131157
} catch(error) {
132158
writeError(error);
133159
}
@@ -137,21 +163,21 @@ if (paths.length === 0) {
137163
* 2. Paths are passed.
138164
*/
139165
var result = Object.create(null);
140-
async.eachSeries(paths, function(path, done) {
141-
fs.stat(path, function(error, stats) {
166+
async.eachSeries(paths, function(filePath, done) {
167+
fs.stat(filePath, function(error, stats) {
142168
if (error) {
143-
writeError(error, path);
169+
writeError(error, filePath);
144170
done();
145171
return;
146172
}
147173
if (stats.isDirectory()) {
148-
traverseDir(path, result, done);
174+
traverseDir(filePath, result, done);
149175
}
150176
else {
151177
try {
152-
result[path] = parser.parse(fs.readFileSync(path));
178+
result[filePath] = parse(fs.readFileSync(filePath));
153179
} catch(error) {
154-
writeError(error, path);
180+
writeError(error, filePath);
155181
}
156182
finally {
157183
done();

example/components/Component.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ var Foo = require('Foo');
55
* General component description.
66
*/
77
var Component = React.createClass({
8+
displayName: 'Component',
9+
810
propTypes: {
911
...Foo.propTypes,
1012
/**

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
"bin",
4747
"src"
4848
],
49+
"testPathIgnorePatterns": [
50+
"/bin/__tests__/example/"
51+
],
4952
"unmockedModulePathPatterns": [
5053
"node_modules",
5154
"tests/utils",

0 commit comments

Comments
 (0)