Update react-docgen
This commit is contained in:
parent
7b0cd86759
commit
74824cb96e
|
@ -4,7 +4,7 @@
|
|||
|
||||
It uses [recast][] 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.
|
||||
|
||||
It provides a default implementation for React components defined via `React.createClass`. These component definitions must follow certain guidelines in order to be analyzable (see below for more info)
|
||||
It provides a default implementation for React components defined via `React.createClass`. These component definitions must follow certain guidelines in order to be analyzable (see below for more info).
|
||||
|
||||
## Install
|
||||
|
||||
|
@ -38,6 +38,9 @@ If a directory is passed, it is recursively traversed.
|
|||
|
||||
By default, `react-docgen` will look for the exported component created through `React.createClass` in each file. Have a look below for how to customize this behavior.
|
||||
|
||||
Have a look at `example/` for an example of how to use the result to generate
|
||||
a markdown version of the documentation.
|
||||
|
||||
## API
|
||||
|
||||
The tool can be used programmatically to extract component information and customize the extraction process:
|
||||
|
@ -209,5 +212,11 @@ The structure of the JSON blob / JavaScript object is as follows:
|
|||
["composes": <componentNames>]
|
||||
}
|
||||
```
|
||||
(`[...]` means the property may not exist if such information was not found in the component definition)
|
||||
|
||||
- `<propName>`: For each prop that was found, there will be an entry in `props` under the same name.
|
||||
- `<typeName>`: The name of the type, which is usually corresponds to the function name in `React.PropTypes`. However, for types define with `oneOf`, we use `"enum"` and for `oneOfType` we use `"union"`. If a custom function is provided or the type cannot be resolved to anything of `React.PropTypes`, we use `"custom"`.
|
||||
- `<typeValue>`: Some types accept parameters which define the type in more detail (such as `arrayOf`, `instanceOf`, `oneOf`, etc). Those are stored in `<typeValue>`. The data type of `<typeValue>` depends on the type definition.
|
||||
|
||||
|
||||
[recast]: https://github.com/benjamn/recast
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This example script expects a JSON blob generated by react-docgen as input,
|
||||
* e.g. react-docgen components/* | buildDocs.sh
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var generateMarkdown = require('./generateMarkdown');
|
||||
var path = require('path');
|
||||
|
||||
var json = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('readable', function() {
|
||||
var chunk = process.stdin.read();
|
||||
if (chunk !== null) {
|
||||
json += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', function() {
|
||||
buildDocs(JSON.parse(json));
|
||||
});
|
||||
|
||||
function buildDocs(api) {
|
||||
// api is an object keyed by filepath. We use the file name as component name.
|
||||
for (var filepath in api) {
|
||||
var name = getComponentName(filepath);
|
||||
var markdown = generateMarkdown(name, api[filepath]);
|
||||
fs.writeFileSync(name + '.md', markdown);
|
||||
process.stdout.write(filepath + ' -> ' + name + '.md\n');
|
||||
}
|
||||
}
|
||||
|
||||
function getComponentName(filepath) {
|
||||
var name = path.basename(filepath);
|
||||
var ext;
|
||||
while ((ext = path.extname(name))) {
|
||||
name = name.substring(0, name.length - ext.length);
|
||||
}
|
||||
return name;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
var React = require('react');
|
||||
var Foo = require('Foo');
|
||||
|
||||
/**
|
||||
* General component description.
|
||||
*/
|
||||
var Component = React.createClass({
|
||||
propTypes: {
|
||||
...Foo.propTypes,
|
||||
/**
|
||||
* Prop description
|
||||
*/
|
||||
bar: React.PropTypes.number
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
bar: 21
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Component;
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* An example for a module that is not a component.
|
||||
*/
|
||||
module.exports = "abc";
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
function stringOfLength(string, length) {
|
||||
var newString = '';
|
||||
for (var i = 0; i < length; i++) {
|
||||
newString += string;
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
function generateTitle(name) {
|
||||
var title = '`' + name + '` (component)';
|
||||
return title + '\n' + stringOfLength('=', title.length) + '\n';
|
||||
}
|
||||
|
||||
function generateDesciption(description) {
|
||||
return description + '\n';
|
||||
}
|
||||
|
||||
function generatePropType(type) {
|
||||
var values;
|
||||
if (Array.isArray(type.value)) {
|
||||
values = '(' +
|
||||
type.value.map(function(typeValue) {
|
||||
return typeValue.name || typeValue.value;
|
||||
}).join('|') +
|
||||
')';
|
||||
} else {
|
||||
values = type.value;
|
||||
}
|
||||
|
||||
return 'type: `' + type.name + (values ? values: '') + '`\n';
|
||||
}
|
||||
|
||||
function generatePropDefaultValue(value) {
|
||||
return 'defaultValue: `' + value.value + '`\n';
|
||||
}
|
||||
|
||||
function generateProp(propName, prop) {
|
||||
return (
|
||||
'### `' + propName + '`' + (prop.required ? ' (required)' : '') + '\n' +
|
||||
'\n' +
|
||||
(prop.description ? prop.description + '\n\n' : '') +
|
||||
(prop.type ? generatePropType(prop.type) : '') +
|
||||
(prop.defaultValue ? generatePropDefaultValue(prop.defaultValue) : '') +
|
||||
'\n'
|
||||
);
|
||||
}
|
||||
|
||||
function generateProps(props) {
|
||||
var title = 'Props';
|
||||
|
||||
return (
|
||||
title + '\n' +
|
||||
stringOfLength('-', title.length) + '\n' +
|
||||
'\n' +
|
||||
Object.keys(props).sort().map(function(propName) {
|
||||
return generateProp(propName, props[propName]);
|
||||
}).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
function generateMarkdown(name, reactAPI) {
|
||||
var markdownString =
|
||||
generateTitle(name) + '\n' +
|
||||
generateDesciption(reactAPI.description) + '\n' +
|
||||
generateProps(reactAPI.props);
|
||||
|
||||
return markdownString;
|
||||
}
|
||||
|
||||
module.exports = generateMarkdown;
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
type Handler = (documentation: Documentation, path: NodePath) => void;
|
||||
|
||||
var Documentation = require('./Documentation');
|
||||
|
||||
var findExportedReactCreateClass =
|
||||
require('./strategies/findExportedReactCreateClassCall');
|
||||
var getPropertyName = require('./utils/getPropertyName');
|
||||
var recast = require('recast');
|
||||
var resolveToValue = require('./utils/resolveToValue');
|
||||
var n = recast.types.namedTypes;
|
||||
|
||||
class ReactDocumentationParser {
|
||||
_componentHandlers: Array<Handler>;
|
||||
_apiHandlers: Object<string, Handler>;
|
||||
|
||||
constructor() {
|
||||
this._componentHandlers = [];
|
||||
this._apiHandlers = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handlers to extract information from the component definition.
|
||||
*
|
||||
* If "property" is not provided, the handler is passed the whole component
|
||||
* definition.
|
||||
*
|
||||
* NOTE: The component definition is currently expected to be represented as
|
||||
* an ObjectExpression (an object literal). This will likely change in the
|
||||
* future.
|
||||
*/
|
||||
addHandler(handler: Handler, property?: string): void {
|
||||
if (!property) {
|
||||
this._componentHandlers.push(handler);
|
||||
} else {
|
||||
if (!this._apiHandlers[property]) {
|
||||
this._apiHandlers[property] = [];
|
||||
}
|
||||
this._apiHandlers[property].push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes JavaScript source code and returns an object with the information
|
||||
* extract from it.
|
||||
*
|
||||
* The second argument is strategy to find the AST node(s) of the component
|
||||
* definition(s) inside `source`.
|
||||
* It is a function that gets passed the program AST node of
|
||||
* the source as first argument, and a reference to recast as second argument.
|
||||
*
|
||||
* This allows you define your own strategy for finding component definitions.
|
||||
* By default it will look for the exported component created by
|
||||
* React.createClass. An error is thrown if multiple components are exported.
|
||||
*
|
||||
* NOTE: The component definition is currently expected to be represented as
|
||||
* an ObjectExpression (an object literal), no matter which strategy is
|
||||
* chosen. This will likely change in the future.
|
||||
*/
|
||||
parseSource(
|
||||
source: string,
|
||||
componentDefinitionStrategy?:
|
||||
(program: ASTNode, recast: Object) => (Array<NodePath>|NodePath)
|
||||
): (Array<Object>|Object) {
|
||||
if (!componentDefinitionStrategy) {
|
||||
componentDefinitionStrategy = findExportedReactCreateClass;
|
||||
}
|
||||
var ast = recast.parse(source);
|
||||
// Find the component definitions first. The return value should be
|
||||
// an ObjectExpression.
|
||||
var componentDefinition = componentDefinitionStrategy(ast.program, recast);
|
||||
var isArray = Array.isArray(componentDefinition);
|
||||
if (!componentDefinition || (isArray && componentDefinition.length === 0)) {
|
||||
throw new Error(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||
}
|
||||
|
||||
return isArray ?
|
||||
this._executeHandlers(componentDefinition).map(
|
||||
documentation => documentation.toObject()
|
||||
) :
|
||||
this._executeHandlers([componentDefinition])[0].toObject();
|
||||
}
|
||||
|
||||
_executeHandlers(componentDefinitions: Array<NodePath>): Array<Documenation> {
|
||||
return componentDefinitions.map(componentDefinition => {
|
||||
var documentation = new Documentation();
|
||||
componentDefinition.get('properties').each(propertyPath => {
|
||||
var name = getPropertyName(propertyPath);
|
||||
if (!this._apiHandlers[name]) {
|
||||
return;
|
||||
}
|
||||
var propertyValuePath = propertyPath.get('value');
|
||||
this._apiHandlers[name].forEach(
|
||||
handler => handler(documentation, propertyValuePath)
|
||||
);
|
||||
});
|
||||
|
||||
this._componentHandlers.forEach(
|
||||
handler => handler(documentation, componentDefinition)
|
||||
);
|
||||
return documentation;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ReactDocumentationParser.ERROR_MISSING_DEFINITION =
|
||||
'No suitable component definition found.';
|
||||
|
||||
module.exports = ReactDocumentationParser;
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('React documentation parser', function() {
|
||||
var ReactDocumentationParser;
|
||||
var parser;
|
||||
var recast;
|
||||
|
||||
beforeEach(function() {
|
||||
recast = require('recast');
|
||||
ReactDocumentationParser = require('../ReactDocumentationParser');
|
||||
parser = new ReactDocumentationParser();
|
||||
});
|
||||
|
||||
function pathFromSource(source) {
|
||||
return new recast.types.NodePath(
|
||||
recast.parse(source).program.body[0].expression
|
||||
);
|
||||
}
|
||||
|
||||
describe('parseSource', function() {
|
||||
|
||||
it('allows custom component definition resolvers', function() {
|
||||
var path = pathFromSource('({foo: "bar"})');
|
||||
var resolver = jest.genMockFunction().mockReturnValue(path);
|
||||
var handler = jest.genMockFunction();
|
||||
parser.addHandler(handler);
|
||||
parser.parseSource('', resolver);
|
||||
|
||||
expect(resolver).toBeCalled();
|
||||
expect(handler.mock.calls[0][1]).toBe(path);
|
||||
});
|
||||
|
||||
it('errors if component definition is not found', function() {
|
||||
var handler = jest.genMockFunction();
|
||||
expect(function() {
|
||||
parser.parseSource('', handler);
|
||||
}).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||
expect(handler).toBeCalled();
|
||||
|
||||
handler = jest.genMockFunction().mockReturnValue([]);
|
||||
expect(function() {
|
||||
parser.parseSource('', handler);
|
||||
}).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
var module_template = [
|
||||
'var React = require("React");',
|
||||
'var PropTypes = React.PropTypes;',
|
||||
'var Component = React.createClass(%s);',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
function getSource(definition) {
|
||||
return module_template.replace('%s', definition);
|
||||
}
|
||||
|
||||
describe('React documentation parser', function() {
|
||||
var parser;
|
||||
|
||||
beforeEach(function() {
|
||||
parser = new (require('../../ReactDocumentationParser'));
|
||||
parser.addHandler(require('../defaultValueHandler'), 'getDefaultProps');
|
||||
});
|
||||
|
||||
it ('should find prop default values that are literals', function() {
|
||||
var source = getSource([
|
||||
'{',
|
||||
' getDefaultProps: function() {',
|
||||
' return {',
|
||||
' foo: "bar",',
|
||||
' bar: 42,',
|
||||
' baz: ["foo", "bar"],',
|
||||
' abc: {xyz: abc.def, 123: 42}',
|
||||
' };',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
defaultValue: {
|
||||
value: '"bar"',
|
||||
computed: false
|
||||
}
|
||||
},
|
||||
bar: {
|
||||
defaultValue: {
|
||||
value: '42',
|
||||
computed: false
|
||||
}
|
||||
},
|
||||
baz: {
|
||||
defaultValue: {
|
||||
value: '["foo", "bar"]',
|
||||
computed: false
|
||||
}
|
||||
},
|
||||
abc: {
|
||||
defaultValue: {
|
||||
value: '{xyz: abc.def, 123: 42}',
|
||||
computed: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
|
@ -25,12 +25,17 @@ describe('propDocblockHandler', function() {
|
|||
});
|
||||
|
||||
function parse(definition) {
|
||||
return utils.parse('(' + definition + ')').get('body', 0, 'expression');
|
||||
var programPath = utils.parse(definition);
|
||||
return programPath.get(
|
||||
'body',
|
||||
programPath.node.body.length - 1,
|
||||
'expression'
|
||||
);
|
||||
}
|
||||
|
||||
it('finds docblocks for prop types', function() {
|
||||
var definition = parse([
|
||||
'{',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
' * Foo comment',
|
||||
|
@ -42,7 +47,7 @@ describe('propDocblockHandler', function() {
|
|||
' */',
|
||||
' bar: Prop.bool,',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
|
@ -58,7 +63,7 @@ describe('propDocblockHandler', function() {
|
|||
|
||||
it('can handle multline comments', function() {
|
||||
var definition = parse([
|
||||
'{',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
' * Foo comment with',
|
||||
|
@ -68,7 +73,7 @@ describe('propDocblockHandler', function() {
|
|||
' */',
|
||||
' foo: Prop.bool',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
|
@ -82,7 +87,7 @@ describe('propDocblockHandler', function() {
|
|||
|
||||
it('ignores non-docblock comments', function() {
|
||||
var definition = parse([
|
||||
'{',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
' * Foo comment',
|
||||
|
@ -96,7 +101,7 @@ describe('propDocblockHandler', function() {
|
|||
' /* This is not a doc comment */',
|
||||
' bar: Prop.bool,',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
|
@ -112,7 +117,7 @@ describe('propDocblockHandler', function() {
|
|||
|
||||
it('only considers the comment with the property below it', function() {
|
||||
var definition = parse([
|
||||
'{',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
' * Foo comment',
|
||||
|
@ -120,7 +125,7 @@ describe('propDocblockHandler', function() {
|
|||
' foo: Prop.bool,',
|
||||
' bar: Prop.bool,',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
|
@ -136,7 +141,7 @@ describe('propDocblockHandler', function() {
|
|||
|
||||
it('understands and ignores the spread operator', function() {
|
||||
var definition = parse([
|
||||
'{',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' ...Foo.propTypes,',
|
||||
' /**',
|
||||
|
@ -144,7 +149,28 @@ describe('propDocblockHandler', function() {
|
|||
' */',
|
||||
' foo: Prop.bool,',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves variables', function() {
|
||||
var definition = parse([
|
||||
'var Props = {',
|
||||
' /**',
|
||||
' * Foo comment',
|
||||
' */',
|
||||
' foo: Prop.bool,',
|
||||
'};',
|
||||
'({',
|
||||
' propTypes: Props',
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
propDocblockHandler(documentation, definition);
|
||||
|
|
|
@ -171,4 +171,21 @@ describe('propTypeHandler', function() {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves variables', function() {
|
||||
var definition = parse([
|
||||
'var props = {bar: PropTypes.bool};',
|
||||
'({',
|
||||
' propTypes: props',
|
||||
'})',
|
||||
].join('\n'));
|
||||
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
bar: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var Documentation = require('../Documentation');
|
||||
|
||||
var expressionTo = require('../utils/expressionTo');
|
||||
var getPropertyName = require('../utils/getPropertyName');
|
||||
var recast = require('recast');
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
var types = recast.types.namedTypes;
|
||||
var visit = recast.types.visit;
|
||||
|
||||
function getDefaultValue(path) {
|
||||
var node = path.node;
|
||||
var defaultValue;
|
||||
if (types.Literal.check(node)) {
|
||||
defaultValue = node.raw;
|
||||
} else {
|
||||
path = resolveToValue(path);
|
||||
node = path.node;
|
||||
defaultValue = recast.print(path).code;
|
||||
}
|
||||
if (typeof defaultValue !== 'undefined') {
|
||||
return {
|
||||
value: defaultValue,
|
||||
computed: types.CallExpression.check(node) ||
|
||||
types.MemberExpression.check(node) ||
|
||||
types.Identifier.check(node)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function defaultValueHandler(documentation: Documentation, path: NodePath) {
|
||||
if (!types.FunctionExpression.check(path.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the value that is returned from the function and process it if it is
|
||||
// an object literal.
|
||||
var objectExpressionPath;
|
||||
visit(path.get('body'), {
|
||||
visitFunction: () => false,
|
||||
visitReturnStatement: function(path) {
|
||||
var resolvedPath = resolveToValue(path.get('argument'));
|
||||
if (types.ObjectExpression.check(resolvedPath.node)) {
|
||||
objectExpressionPath = resolvedPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (objectExpressionPath) {
|
||||
objectExpressionPath.get('properties').each(function(propertyPath) {
|
||||
var propDescriptor = documentation.getPropDescriptor(
|
||||
getPropertyName(propertyPath)
|
||||
);
|
||||
var defaultValue = getDefaultValue(propertyPath.get('value'));
|
||||
if (defaultValue) {
|
||||
propDescriptor.defaultValue = defaultValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = defaultValueHandler;
|
|
@ -15,13 +15,14 @@
|
|||
|
||||
var Documentation = require('../Documentation');
|
||||
|
||||
var types = require('recast').types.namedTypes;
|
||||
var getDocblock = require('../utils/docblock').getDocblock;
|
||||
var getPropertyName = require('../utils/getPropertyName');
|
||||
var getPropertyValuePath = require('../utils/getPropertyValuePath');
|
||||
var types = require('recast').types.namedTypes;
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
|
||||
function propDocBlockHandler(documentation: Documentation, path: NodePath) {
|
||||
var propTypesPath = getPropertyValuePath(path, 'propTypes');
|
||||
var propTypesPath = resolveToValue(getPropertyValuePath(path, 'propTypes'));
|
||||
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ function amendPropTypes(documentation, path) {
|
|||
}
|
||||
|
||||
function propTypeHandler(documentation: Documentation, path: NodePath) {
|
||||
var propTypesPath = getPropertyValuePath(resolveToValue(path), 'propTypes');
|
||||
var propTypesPath = resolveToValue(getPropertyValuePath(path, 'propTypes'));
|
||||
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('React documentation parser', function() {
|
||||
var findAllReactCreateClassCalls;
|
||||
var recast;
|
||||
|
||||
function parse(source) {
|
||||
return findAllReactCreateClassCalls(
|
||||
recast.parse(source).program,
|
||||
recast
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
findAllReactCreateClassCalls = require('../findAllReactCreateClassCalls');
|
||||
recast = require('recast');
|
||||
});
|
||||
|
||||
|
||||
it('finds React.createClass', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
var result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0] instanceof recast.types.NodePath).toBe(true);
|
||||
expect(result[0].node.type).toBe('ObjectExpression');
|
||||
});
|
||||
|
||||
it('finds React.createClass, independent of the var name', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var Component = R.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
var result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(1);
|
||||
});
|
||||
|
||||
it('does not process X.createClass of other modules', function() {
|
||||
var source = [
|
||||
'var R = require("NoReact");',
|
||||
'var Component = R.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
var result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
it('finds assignments to exports', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var Component = R.createClass({});',
|
||||
'exports.foo = 42;',
|
||||
'exports.Component = Component;'
|
||||
].join('\n');
|
||||
|
||||
var result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(1);
|
||||
});
|
||||
|
||||
it('accepts multiple definitions', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var ComponentA = R.createClass({});',
|
||||
'var ComponentB = R.createClass({});',
|
||||
'exports.ComponentB = ComponentB;'
|
||||
].join('\n');
|
||||
|
||||
var result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(2);
|
||||
|
||||
source = [
|
||||
'var R = require("React");',
|
||||
'var ComponentA = R.createClass({});',
|
||||
'var ComponentB = R.createClass({});',
|
||||
'module.exports = ComponentB;'
|
||||
].join('\n');
|
||||
|
||||
result = parse(source);
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(result.length).toBe(2);
|
||||
});
|
||||
});
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
describe('React documentation parser', function() {
|
||||
var findExportedReactCreateClass;
|
||||
var recast;
|
||||
|
||||
function parse(source) {
|
||||
return findExportedReactCreateClass(
|
||||
recast.parse(source).program,
|
||||
recast
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
findExportedReactCreateClass =
|
||||
require('../findExportedReactCreateClassCall');
|
||||
recast = require('recast');
|
||||
});
|
||||
|
||||
it('finds React.createClass', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeDefined();
|
||||
});
|
||||
|
||||
it('finds React.createClass, independent of the var name', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var Component = R.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeDefined();
|
||||
});
|
||||
|
||||
it('does not process X.createClass of other modules', function() {
|
||||
var source = [
|
||||
'var R = require("NoReact");',
|
||||
'var Component = R.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('finds assignments to exports', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var Component = R.createClass({});',
|
||||
'exports.foo = 42;',
|
||||
'exports.Component = Component;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeDefined();
|
||||
});
|
||||
|
||||
it('errors if multiple components are exported', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var ComponentA = R.createClass({});',
|
||||
'var ComponentB = R.createClass({});',
|
||||
'exports.ComponentA = ComponentA;',
|
||||
'exports.ComponentB = ComponentB;'
|
||||
].join('\n');
|
||||
|
||||
expect(function() {
|
||||
parse(source)
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('accepts multiple definitions if only one is exported', function() {
|
||||
var source = [
|
||||
'var R = require("React");',
|
||||
'var ComponentA = R.createClass({});',
|
||||
'var ComponentB = R.createClass({});',
|
||||
'exports.ComponentB = ComponentB;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeDefined();
|
||||
|
||||
source = [
|
||||
'var R = require("React");',
|
||||
'var ComponentA = R.createClass({});',
|
||||
'var ComponentB = R.createClass({});',
|
||||
'module.exports = ComponentB;'
|
||||
].join('\n');
|
||||
|
||||
expect(parse(source)).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var isReactCreateClassCall = require('../utils/isReactCreateClassCall');
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
|
||||
/**
|
||||
* Given an AST, this function tries to find all object expressions that are
|
||||
* passed to `React.createClass` calls, by resolving all references properly.
|
||||
*/
|
||||
function findAllReactCreateClassCalls(
|
||||
ast: ASTNode,
|
||||
recast: Object
|
||||
): Array<NodePath> {
|
||||
var types = recast.types.namedTypes;
|
||||
var definitions = [];
|
||||
|
||||
recast.visit(ast, {
|
||||
visitCallExpression: function(path) {
|
||||
if (!isReactCreateClassCall(path)) {
|
||||
return false;
|
||||
}
|
||||
// We found React.createClass. Lets get cracking!
|
||||
var resolvedPath = resolveToValue(path.get('arguments', 0));
|
||||
if (types.ObjectExpression.check(resolvedPath.node)) {
|
||||
definitions.push(resolvedPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
module.exports = findAllReactCreateClassCalls;
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var isExportsOrModuleAssignment =
|
||||
require('../utils/isExportsOrModuleAssignment');
|
||||
var isReactCreateClassCall = require('../utils/isReactCreateClassCall');
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
|
||||
var ERROR_MULTIPLE_DEFINITIONS =
|
||||
'Multiple exported component definitions found.';
|
||||
|
||||
function ignore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an AST, this function tries to find the object expression that is
|
||||
* passed to `React.createClass`, by resolving all references properly.
|
||||
*/
|
||||
function findExportedReactCreateClass(
|
||||
ast: ASTNode,
|
||||
recast: Object
|
||||
): ?NodePath {
|
||||
var types = recast.types.namedTypes;
|
||||
var definition;
|
||||
|
||||
recast.visit(ast, {
|
||||
visitFunctionDeclaration: ignore,
|
||||
visitFunctionExpression: ignore,
|
||||
visitIfStatement: ignore,
|
||||
visitWithStatement: ignore,
|
||||
visitSwitchStatement: ignore,
|
||||
visitCatchCause: ignore,
|
||||
visitWhileStatement: ignore,
|
||||
visitDoWhileStatement: ignore,
|
||||
visitForStatement: ignore,
|
||||
visitForInStatement: ignore,
|
||||
visitAssignmentExpression: function(path) {
|
||||
// Ignore anything that is not `exports.X = ...;` or
|
||||
// `module.exports = ...;`
|
||||
if (!isExportsOrModuleAssignment(path)) {
|
||||
return false;
|
||||
}
|
||||
// Resolve the value of the right hand side. It should resolve to a call
|
||||
// expression, something like React.createClass
|
||||
path = resolveToValue(path.get('right'));
|
||||
if (!isReactCreateClassCall(path)) {
|
||||
return false;
|
||||
}
|
||||
if (definition) {
|
||||
// If a file exports multiple components, ... complain!
|
||||
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
|
||||
}
|
||||
// We found React.createClass. Lets get cracking!
|
||||
var resolvedPath = resolveToValue(path.get('arguments', 0));
|
||||
if (types.ObjectExpression.check(resolvedPath.node)) {
|
||||
definition = resolvedPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
module.exports = findExportedReactCreateClass;
|
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
"name": "react-docgen",
|
||||
"version": "1.0.0",
|
||||
"description": "Extract information from React components for documentation generation",
|
||||
"description": "A CLI and toolkit to extract information from React components for documentation generation.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactjs/react-docgen.git"
|
||||
},
|
||||
"bugs": "https://github.com/reactjs/react-docgen/issues",
|
||||
"bin": {
|
||||
"react-docgen": "bin/react-docgen.js"
|
||||
},
|
||||
|
@ -14,7 +19,7 @@
|
|||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"documentation"
|
||||
"documentation-generation"
|
||||
],
|
||||
"author": "Felix Kling",
|
||||
"license": "BSD-3-Clause",
|
||||
|
|
Loading…
Reference in New Issue