Update react-docgen

This commit is contained in:
Felix Kling 2015-03-02 17:31:26 -08:00
parent 7b51a09444
commit f77d9f8bae
39 changed files with 1785 additions and 774 deletions

View File

@ -0,0 +1,43 @@
# Contributing to react-docgen
We want to make contributing to this project as easy and transparent as
possible.
## Our Development Process
The majority of development on react-docgen will occur through GitHub. Accordingly,
the process for contributing will follow standard GitHub protocol.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints and typechecks.
6. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## Coding Style
* Use semicolons;
* Commas last,
* 2 spaces for indentation (no tabs)
* Prefer `'` over `"`
* `"use strict";`
* 80 character line length
* "Attractive"
## License
By contributing to react-docgen, you agree that your contributions will be licensed
under its BSD license.

View File

@ -1,15 +1,10 @@
# react-docgen # react-docgen
`react-docgen` extracts information from React components with which `react-docgen` is a CLI and toolbox to help extracting information from React components, and generate documentation from it.
you can generate documentation for those components.
It uses [recast][] to parse the provided files into an AST, looks for React 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.
component definitions, and inspects the `propTypes` and `getDefaultProps`
declarations. The output is a JSON blob with the extracted information.
Note that component definitions must follow certain guidelines in order to be 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)
analyzable by this tool. We will work towards less strict guidelines, but there
is a limit to what is statically analyzable.
## Install ## Install
@ -41,22 +36,62 @@ Extract meta information from React components.
If a directory is passed, it is recursively traversed. 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.
## API ## API
The tool can also be used programmatically to extract component information: The tool can be used programmatically to extract component information and customize the extraction process:
```js ```js
var reactDocs = require('react-docgen'); var reactDocs = require('react-docgen');
var componentInfo reactDocs.parseSource(src); var componentInfo = reactDocs.parse(src);
``` ```
## Guidelines As with the CLI, this will look for the exported component created through `React.createClass` in the provided source. The whole process of analyzing the source code is separated into two parts:
- Locating/finding the nodes in the AST which define the component
- Extracting information from those nodes
`parse` accepts more arguments with which this behavior can be customized.
### parse(source \[, resolver \[, handlers\]\])
| Parameter | Type | Description |
| -------------- | ------ | --------------- |
| source | string | The source text |
| resolver | function | A function of the form `(ast: ASTNode, recast: Object) => (NodePath|Array<NodePath>)`. Given an AST and a reference to recast, it returns an (array of) NodePath which represents the component definition. |
| handlers | Array\<function\> | An array of functions of the form `(documentation: Documentation, definition: NodePath) => void`. Each function is called with a `Documentation` object and a reference to the component definition as returned by `resolver`. Handlers extract relevant information from the definition and augment `documentation`.
#### resolver
The resolver's task is to extract those parts from the source code which the handlers can analyze. For example, the `findExportedReactCreateClassCall` resolver inspects the AST to find
```js
var Component = React.createClass(<def>);
module.exports = Component;
```
and returns the ObjectExpression to which `<def>` resolves.
`findAllReactCreateClassCalls` works similarly, but simply finds all `React.createClass` calls, not only the one that creates the exported component.
This makes it easy, together with the utility methods created to analyze the AST, to introduce new or custom resolver methods. For example, a resolver could look for plain ObjectExpressions with a `render` method or `class Component extends React.Component` instead (**note:** a default resolver for `class` based react components is planned).
#### handlers
Handlers do the actual work and extract the desired information from the result the resolver returned. Like the resolver, they try to delegate as much work as possible to the reusable utility functions.
For example, while the `propTypesHandler` expects the prop types definition to be an ObjectExpression and be located inside an ObjectExpression under the property name `propTypes`, most of the work is actually performed by the `getPropType` utility function.
## Guidelines for default resolvers and handlers
- Modules have to export a single component, and only that component is - Modules have to export a single component, and only that component is
analyzed. analyzed.
- The component definition must be an object literal.
- `propTypes` must be an object literal or resolve to an object literal in the - `propTypes` must be an object literal or resolve to an object literal in the
same file. same file.
- The `return` statement in `getDefaultProps` must consist of an object literal. - The `return` statement in `getDefaultProps` must contain an object literal.
## Example ## Example

View File

@ -96,7 +96,7 @@ if (paths.length === 0) {
source += chunk; source += chunk;
}); });
process.stdin.on('end', function () { process.stdin.on('end', function () {
exitWithResult(parser.parseSource(source)); exitWithResult(parser.parse(source));
}); });
} }
@ -112,7 +112,7 @@ function traverseDir(path, result, done) {
exitWithError(error); exitWithError(error);
} }
try { try {
result[filename] = parser.parseSource(content); result[filename] = parser.parse(content);
} catch(error) { } catch(error) {
writeError(error, path); writeError(error, path);
} }
@ -143,7 +143,7 @@ async.eachSeries(paths, function(path, done) {
} }
else { else {
try { try {
result[path] = parser.parseSource(fs.readFileSync(path)); result[path] = parser.parse(fs.readFileSync(path));
} catch(error) { } catch(error) {
writeError(error, path); writeError(error, path);
} }

View File

@ -0,0 +1,35 @@
/*
* 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.
*
*/
type PropTypeDescriptor = {
name: string;
value?: any;
raw?: string;
};
type PropDescriptor = {
type?: PropTypeDescriptor;
required?: boolean;
defaultValue?: any;
description?: string;
};
declare class Documentation {
addComposes(moduleName: string): void;
getDescription(): string;
setDescription(description: string): void;
getPropDescriptor(propName: string): PropDescriptor;
toObject(): Object;
}
type Handler = (documentation: Documentation, path: NodePath) => void;
type Resolver =
(node: ASTNode, recast: Recast) => (NodePath|Array<NodePath>|void);

View File

@ -28,3 +28,8 @@ declare class NodePath {
each(f: (p: NodePath) => void): void; each(f: (p: NodePath) => void): void;
map<T>(f: (p: NodePath) => T): Array<T>; map<T>(f: (p: NodePath) => T): Array<T>;
} }
type Recast = {
parse: (src: string) => ASTNode;
print: (path: NodePath) => {code: string};
};

View File

@ -13,17 +13,6 @@
*/ */
"use strict"; "use strict";
type PropDescriptor = {
type?: {
name: string;
value?: any;
raw?: string;
};
required?: boolean;
defaultValue?: any;
description?: string;
};
class Documentation { class Documentation {
_props: Object; _props: Object;
_description: string; _description: string;

View File

@ -0,0 +1,28 @@
/*
* 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.
*
*/
function Documentation() {
return {
description: '',
composes: [],
descriptors: {},
getPropDescriptor(name) {
return this.descriptors[name] || (this.descriptors[name] = {});
},
addComposes(name) {
this.composes.push(name);
},
setDescription(descr) {
this.description = descr;
}
};
}
module.exports = Documentation;

View File

@ -0,0 +1,75 @@
/*
* 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 source = [
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'/**',
' * Example component description',
' */',
'var Component = React.createClass({',
' propTypes: {',
' /**',
' * Example prop description',
' */',
' foo: PropTypes.bool',
' },',
' getDefaultProps: function() {',
' return {',
' foo: true',
' };',
' }',
'});',
'module.exports = Component;'
].join('\n');
describe('main', function() {
var utils;
var docgen;
beforeEach(function() {
utils = require('../../tests/utils');
docgen = require('../main');
});
it('parses with default resolver/handlers', function() {
var docs = docgen.parse(source);
expect(docs).toEqual({
description: 'Example component description',
props: {
foo: {
type: {
name: 'bool'
},
defaultValue: {
computed: false,
value: 'true'
},
description: 'Example prop description',
required: false
}
}
});
});
it('parses with custom handlers', function() {
var docs = docgen.parse(source, null, [
docgen.handlers.componentDocblockHandler,
]);
expect(docs).toEqual({
description: 'Example component description',
props: {}
});
});
});

View File

@ -0,0 +1,52 @@
/*
* 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('parse', function() {
var utils;
var parse;
beforeEach(function() {
utils = require('../../tests/utils');
parse = require('../parse');
});
function pathFromSource(source) {
return utils.parse(source).get('body', 0, 'expression');
}
it('allows custom component definition resolvers', function() {
var path = pathFromSource('({foo: "bar"})');
var resolver = jest.genMockFunction().mockReturnValue(path);
var handler = jest.genMockFunction();
parse('', resolver, [handler]);
expect(resolver).toBeCalled();
expect(handler.mock.calls[0][1]).toBe(path);
});
it('errors if component definition is not found', function() {
var resolver = jest.genMockFunction();
expect(function() {
parse('', resolver);
}).toThrow(parse.ERROR_MISSING_DEFINITION);
expect(resolver).toBeCalled();
handler = jest.genMockFunction().mockReturnValue([]);
expect(function() {
parse('', resolver);
}).toThrow(parse.ERROR_MISSING_DEFINITION);
expect(resolver).toBeCalled();
});
});

View File

@ -11,86 +11,74 @@
"use strict"; "use strict";
jest.autoMockOff(); jest.autoMockOff();
jest.mock('../../Documentation');
describe('React documentation parser', function() { describe('componentDocblockHandler', function() {
var parser; var utils;
var documentation;
var componentDocblockHandler;
function parse(src) {
var programPath = utils.parse(src);
return programPath.get(
'body',
programPath.node.body.length - 1,
'declarations',
0,
'init',
'arguments',
0
);
}
beforeEach(function() { beforeEach(function() {
parser = new (require('../../ReactDocumentationParser')); utils = require('../../../tests/utils');
parser.addHandler(require('../componentDocblockHandler')); documentation = new (require('../../Documentation'));
componentDocblockHandler = require('../componentDocblockHandler');
}); });
it('finds docblocks for component definitions', function() { it('finds docblocks for component definitions', function() {
var source = [ var definition = parse([
'var React = require("React");',
'/**', '/**',
' * Component description', ' * Component description',
' */', ' */',
'var Component = React.createClass({});', 'var Component = React.createClass({});',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = { componentDocblockHandler(documentation, definition);
props: {}, expect(documentation.description).toBe('Component description');
description: 'Component description'
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('ignores other types of comments', function() { it('ignores other types of comments', function() {
var source = [ var definition = parse([
'var React = require("React");',
'/*', '/*',
' * This is not a docblock', ' * This is not a docblock',
' */', ' */',
'var Component = React.createClass({});', 'var Component = React.createClass({});',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = { componentDocblockHandler(documentation, definition);
props: {}, expect(documentation.description).toBe('');
description: ''
};
var result = parser.parseSource(source); definition = parse([
expect(result).toEqual(expectedResult);
source = [
'var React = require("React");',
'// Inline comment', '// Inline comment',
'var Component = React.createClass({});', 'var Component = React.createClass({});',
'module.exports = Component;' ].join('\n'));
].join('\n');
expectedResult = { componentDocblockHandler(documentation, definition);
props: {}, expect(documentation.description).toBe('');
description: ''
};
result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('only considers the docblock directly above the definition', function() { it('only considers the docblock directly above the definition', function() {
var source = [ var definition = parse([
'var React = require("React");',
'/**', '/**',
' * This is the wrong docblock', ' * This is the wrong docblock',
' */', ' */',
'var something_else = "foo";', 'var something_else = "foo";',
'var Component = React.createClass({});', 'var Component = React.createClass({});',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = { componentDocblockHandler(documentation, definition);
props: {}, expect(documentation.description).toBe('');
description: ''
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
}); });

View File

@ -0,0 +1,73 @@
/*
* 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();
jest.mock('../../Documentation');
describe('defaultPropsHandler', function() {
var utils;
var documentation;
var defaultValueHandler;
function parse(src) {
return utils.parse(src).get('body', 0, 'expression');
}
beforeEach(function() {
utils = require('../../../tests/utils');
documentation = new (require('../../Documentation'));
defaultPropsHandler = require('../defaultPropsHandler');
});
it ('should find prop default values that are literals', function() {
var definition = parse([
'({',
' getDefaultProps: function() {',
' return {',
' foo: "bar",',
' bar: 42,',
' baz: ["foo", "bar"],',
' abc: {xyz: abc.def, 123: 42}',
' };',
' }',
'});'
].join('\n'));
defaultPropsHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
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
}
}
});
});
});

View File

@ -11,28 +11,25 @@
"use strict"; "use strict";
jest.autoMockOff(); jest.autoMockOff();
jest.mock('../../Documentation');
var module_template = [ describe('propDocblockHandler', function() {
'var React = require("React");', var utils;
'var PropTypes = React.PropTypes;', var documentation;
'var Component = React.createClass(%s);', var propDocblockHandler;
'module.exports = Component;'
].join('\n');
function getSource(definition) {
return module_template.replace('%s', definition);
}
describe('React documentation parser', function() {
var parser;
beforeEach(function() { beforeEach(function() {
parser = new (require('../../ReactDocumentationParser')); utils = require('../../../tests/utils');
parser.addHandler(require('../propDocblockHandler'), 'propTypes'); documentation = new (require('../../Documentation'));
propDocblockHandler = require('../propDocblockHandler');
}); });
function parse(definition) {
return utils.parse('(' + definition + ')').get('body', 0, 'expression');
}
it('finds docblocks for prop types', function() { it('finds docblocks for prop types', function() {
var source = getSource([ var definition = parse([
'{', '{',
' propTypes: {', ' propTypes: {',
' /**', ' /**',
@ -48,24 +45,19 @@ describe('React documentation parser', function() {
'}' '}'
].join('\n')); ].join('\n'));
var expectedResult = { propDocblockHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { foo: {
foo: { description: 'Foo comment'
description: 'Foo comment' },
}, bar: {
bar: { description: 'Bar comment'
description: 'Bar comment'
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('can handle multline comments', function() { it('can handle multline comments', function() {
var source = getSource([ var definition = parse([
'{', '{',
' propTypes: {', ' propTypes: {',
' /**', ' /**',
@ -79,22 +71,17 @@ describe('React documentation parser', function() {
'}' '}'
].join('\n')); ].join('\n'));
var expectedResult = { propDocblockHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { foo: {
foo: { description:
description:
'Foo comment with\nmany lines!\n\neven with empty lines in between' 'Foo comment with\nmany lines!\n\neven with empty lines in between'
} },
} });
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('ignores non-docblock comments', function() { it('ignores non-docblock comments', function() {
var source = getSource([ var definition = parse([
'{', '{',
' propTypes: {', ' propTypes: {',
' /**', ' /**',
@ -112,24 +99,19 @@ describe('React documentation parser', function() {
'}' '}'
].join('\n')); ].join('\n'));
var expectedResult = { propDocblockHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { foo: {
foo: { description: 'Foo comment'
description: 'Foo comment' },
}, bar: {
bar: { description: 'Bar comment'
description: 'Bar comment'
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('only considers the comment with the property below it', function() { it('only considers the comment with the property below it', function() {
var source = getSource([ var definition = parse([
'{', '{',
' propTypes: {', ' propTypes: {',
' /**', ' /**',
@ -141,24 +123,19 @@ describe('React documentation parser', function() {
'}' '}'
].join('\n')); ].join('\n'));
var expectedResult = { propDocblockHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { foo: {
foo: { description: 'Foo comment'
description: 'Foo comment' },
}, bar: {
bar: { description: ''
description: ''
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('understands and ignores the spread operator', function() { it('understands and ignores the spread operator', function() {
var source = getSource([ var definition = parse([
'{', '{',
' propTypes: {', ' propTypes: {',
' ...Foo.propTypes,', ' ...Foo.propTypes,',
@ -166,24 +143,15 @@ describe('React documentation parser', function() {
' * Foo comment', ' * Foo comment',
' */', ' */',
' foo: Prop.bool,', ' foo: Prop.bool,',
' bar: Prop.bool,',
' }', ' }',
'}' '}'
].join('\n')); ].join('\n'));
var expectedResult = { propDocblockHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { foo: {
foo: { description: 'Foo comment'
description: 'Foo comment'
},
bar: {
description: ''
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
}); });

View File

@ -11,464 +11,164 @@
"use strict"; "use strict";
jest.autoMockOff(); jest.autoMockOff();
jest.mock('../../Documentation');
var module_template = [ describe('propTypeHandler', function() {
'var React = require("React");', var utils;
'var PropTypes = React.PropTypes;', var getPropTypeMock;
'var Component = React.createClass(%s);', var documentation;
'module.exports = Component;' var propTypeHandler;
].join('\n');
function getSource(definition) {
return module_template.replace('%s', definition);
}
describe('React documentation parser', function() {
var parser;
beforeEach(function() { beforeEach(function() {
parser = new (require('../../ReactDocumentationParser')); utils = require('../../../tests/utils');
parser.addHandler(require('../propTypeHandler'), 'propTypes'); getPropTypeMock = jest.genMockFunction().mockImplementation(() => ({}));
jest.setMock('../../utils/getPropType', getPropTypeMock);
jest.mock('../../utils/getPropType');
documentation = new (require('../../Documentation'));
propTypeHandler = require('../propTypeHandler');
});
function parse(definition) {
var programPath = utils.parseWithTemplate(definition, utils.REACT_TEMPLATE);
return programPath.get(
'body',
programPath.node.body.length - 1,
'expression'
);
}
it('passes the correct argument to getPropType', function() {
var definition = parse(
'({propTypes: {foo: PropTypes.bool, abc: PropTypes.xyz}})'
);
var propertyPath = definition.get('properties', 0, 'value');
var fooPath = propertyPath.get('properties', 0, 'value');
var xyzPath = propertyPath.get('properties', 1, 'value');
propTypeHandler(documentation, definition);
expect(getPropTypeMock).toBeCalledWith(fooPath);
expect(getPropTypeMock).toBeCalledWith(xyzPath);
}); });
it('finds definitions via React.PropTypes', function() { it('finds definitions via React.PropTypes', function() {
var source = [ var definition = parse([
'var React = require("React");', '({',
'var Prop = React.PropTypes;',
'var Prop1 = require("React").PropTypes;',
'var Component = React.createClass({',
' propTypes: {', ' propTypes: {',
' foo: Prop.bool,', ' foo: PropTypes.bool,',
' bar: Prop1.bool,', ' bar: require("react").PropTypes.bool,',
' }', ' }',
'});', '})',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = {
description: '', propTypeHandler(documentation, definition);
props: { expect(documentation.descriptors).toEqual({
foo: { foo: {
type: {name: 'bool'}, type: {},
required: false required: false
}, },
bar: { bar: {
type: {name: 'bool'}, type: {},
required: false required: false
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('finds definitions via the ReactPropTypes module', function() { it('finds definitions via the ReactPropTypes module', function() {
var source = [ var definition = parse([
'var React = require("React");', '({',
'var Prop = require("ReactPropTypes");',
'var Component = React.createClass({',
' propTypes: {', ' propTypes: {',
' foo: Prop.bool,', ' foo: require("ReactPropTypes").bool,',
' }', ' }',
'});', '})',
'module.exports = Component;'
].join('\n');
var expectedResult = {
description: '',
props: {
foo: {
type: {name: 'bool'},
required: false
}
}
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
});
it('detects simple prop types', function() {
var source = getSource([
'{',
' propTypes: {',
' array_prop: PropTypes.array,',
' bool_prop: PropTypes.bool,',
' func_prop: PropTypes.func,',
' number_prop: PropTypes.number,',
' object_prop: PropTypes.object,',
' string_prop: PropTypes.string,',
' element_prop: PropTypes.element,',
' any_prop: PropTypes.any,',
' node_prop: PropTypes.node',
' }',
'}'
].join('\n')); ].join('\n'));
var expectedResult = {
description: '',
props:{
array_prop: {
type: {name: 'array'},
required: false
},
bool_prop: {
type: {name: 'bool'},
required: false
},
func_prop: {
type: {name: 'func'},
required: false
},
number_prop: {
type: {name: 'number'},
required: false
},
object_prop: {
type: {name: 'object'},
required: false
},
string_prop: {
type: {name: 'string'},
required: false
},
element_prop: {
type: {name: 'element'},
required: false
},
any_prop: {
type: {name: 'any'},
required: false
},
node_prop: {
type: {name: 'node'},
required: false
}
}
};
var result = parser.parseSource(source); propTypeHandler(documentation, definition);
expect(result).toEqual(expectedResult); expect(documentation.descriptors).toEqual({
}); foo: {
type: {},
it('detects complex prop types', function() { required: false
var source = getSource([ },
'{', });
' propTypes: {',
' oneOf_prop: PropTypes.oneOf(["foo", "bar"]),',
' oneOfType_prop:',
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),',
' oneOfType_custom_prop:',
' PropTypes.oneOfType([xyz]),',
' instanceOf_prop: PropTypes.instanceOf(Foo),',
' arrayOf_prop: PropTypes.arrayOf(PropTypes.string),',
' shape_prop:',
' PropTypes.shape({foo: PropTypes.string, bar: PropTypes.bool}),',
' shape_custom_prop:',
' PropTypes.shape({foo: xyz})',
' }',
'}'
].join('\n'));
var expectedResult = {
description: '',
props:{
oneOf_prop: {
type: {
name: 'enum',
value: [
{value: '"foo"', computed: false},
{value: '"bar"', computed: false}
]
},
required: false
},
oneOfType_prop: {
type: {
name:'union',
value: [
{name: 'number'},
{name: 'bool'}
]
},
required: false
},
oneOfType_custom_prop: {
type: {
name:'union',
value: [{
name: 'custom',
raw: 'xyz'
}]
},
required: false
},
instanceOf_prop: {
type: {
name: 'instance',
value: 'Foo'
},
required: false
},
arrayOf_prop: {
type: {
name: 'arrayof',
value: {name: 'string'}
},
required: false
},
shape_prop: {
type: {
name: 'shape',
value: {
foo: {name: 'string'},
bar: {name: 'bool'}
}
},
required: false
},
shape_custom_prop: {
type: {
name: 'shape',
value: {
foo: {
name: 'custom',
raw: 'xyz'
},
}
},
required: false
}
}
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
});
it('resolves variables to their values', function() {
var source = [
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'var shape = {bar: PropTypes.string};',
'var Component = React.createClass({',
' propTypes: {',
' foo: PropTypes.shape(shape)',
' }',
'});',
'module.exports = Component;'
].join('\n');
var expectedResult = {
description: '',
props: {
foo: {
type: {
name: 'shape',
value: {
bar: {name: 'string'}
}
},
required: false
}
}
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('detects whether a prop is required', function() { it('detects whether a prop is required', function() {
var source = getSource([ var definition = parse([
'{', '({',
' propTypes: {', ' propTypes: {',
' array_prop: PropTypes.array.isRequired,', ' simple_prop: PropTypes.array.isRequired,',
' bool_prop: PropTypes.bool.isRequired,', ' complex_prop: ',
' func_prop: PropTypes.func.isRequired,',
' number_prop: PropTypes.number.isRequired,',
' object_prop: PropTypes.object.isRequired,',
' string_prop: PropTypes.string.isRequired,',
' element_prop: PropTypes.element.isRequired,',
' any_prop: PropTypes.any.isRequired,',
' oneOf_prop: PropTypes.oneOf(["foo", "bar"]).isRequired,',
' oneOfType_prop: ',
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired,', ' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired,',
' instanceOf_prop: PropTypes.instanceOf(Foo).isRequired',
' }', ' }',
'}' '})'
].join('\n')); ].join('\n'));
var expectedResult = { propTypeHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props:{ simple_prop: {
array_prop: { type: {},
type: {name: 'array'},
required: true required: true
}, },
bool_prop: { complex_prop: {
type: {name: 'bool'}, type: {},
required: true
},
func_prop: {
type: {name: 'func'},
required: true
},
number_prop: {
type: {name: 'number'},
required: true
},
object_prop: {
type: {name: 'object'},
required: true
},
string_prop: {
type: {name: 'string'},
required: true
},
element_prop: {
type: {name: 'element'},
required: true
},
any_prop: {
type: {name: 'any'},
required: true
},
oneOf_prop: {
type: {
name: 'enum',
value: [
{value: '"foo"', computed: false},
{value: '"bar"', computed: false}
]
},
required: true
},
oneOfType_prop: {
type: {
name: 'union',
value: [
{name: 'number'},
{name: 'bool'}
]
},
required: true
},
instanceOf_prop: {
type: {
name: 'instance',
value: 'Foo'
},
required: true required: true
} }
} });
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
});
it('detects custom validation functions', function() {
var source = getSource([
'{',
' propTypes: {',
' custom_prop: function() {},',
' custom_prop2: () => {}',
' }',
'}'
].join('\n'));
var expectedResult = {
description: '',
props: {
custom_prop: {
type: {
name: 'custom',
raw: 'function() {}'
},
required: false
},
custom_prop2: {
type: {
name: 'custom',
raw: '() => {}'
},
required: false
}
}
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('only considers definitions from React or ReactPropTypes', function() { it('only considers definitions from React or ReactPropTypes', function() {
var source = [ var definition = parse([
'var React = require("React");', '({',
'var PropTypes = React.PropTypes;',
'var Prop = require("Foo");',
'var Component = React.createClass({',
' propTypes: {', ' propTypes: {',
' custom_propA: PropTypes.bool,', ' custom_propA: PropTypes.bool,',
' custom_propB: Prop.bool.isRequired', ' custom_propB: Prop.bool.isRequired',
' }', ' }',
'});', '})',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = { propTypeHandler(documentation, definition);
description: '', expect(documentation.descriptors).toEqual({
props: { custom_propA: {
custom_propA: { type: {},
type: {name: 'bool'}, required: false
required: false },
custom_propB: {
type: {
name: 'custom',
raw: 'Prop.bool.isRequired'
}, },
custom_propB: { required: false
type: {
name: 'custom',
raw: 'Prop.bool.isRequired'
},
required: false
}
} }
}; });
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
it('understands the spread operator', function() { it('understands the spread operator', function() {
var source = [ var definition = parse([
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'var Foo = require("Foo.react");', 'var Foo = require("Foo.react");',
'var props = {bar: PropTypes.bool};', 'var props = {bar: PropTypes.bool};',
'var Component = React.createClass({', '({',
' propTypes: {', ' propTypes: {',
' ...Foo.propTypes,', ' ...Foo.propTypes,',
' ...props,', ' ...props,',
' foo: PropTypes.number', ' foo: PropTypes.number',
' }', ' }',
'});', '})',
'module.exports = Component;' ].join('\n'));
].join('\n');
var expectedResult = { propTypeHandler(documentation, definition);
description: '', expect(documentation.composes).toEqual(['Foo.react']);
composes: ['Foo.react'], expect(documentation.descriptors).toEqual({
props:{ foo: {
foo: { type: {},
type: {name: 'number'}, required: false
required: false },
}, bar: {
bar: { type: {},
type: {name: 'bool'}, required: false
required: false },
}, });
}
};
var result = parser.parseSource(source);
expect(result).toEqual(expectedResult);
}); });
}); });

View File

@ -25,15 +25,26 @@ function componentDocblockHandler(
documentation: Documentation, documentation: Documentation,
path: NodePath path: NodePath
) { ) {
var description = ''; var description = null;
// Find parent statement (e.g. var Component = React.createClass(path);) // Find parent statement (e.g. var Component = React.createClass(<path>);)
while (path && !n.Statement.check(path.node)) { while (path && !n.Statement.check(path.node)) {
path = path.parent; path = path.parent;
} }
if (path) { if (path) {
description = getDocblock(path) || ''; description = getDocblock(path);
} }
documentation.setDescription(description); if (description == null) {
// If this is the first statement in the module body, the comment is attached
// to the program node
var programPath = path;
while (programPath && !n.Program.check(programPath.node)) {
programPath = programPath.parent;
}
if (programPath.get('body', 0) === path) {
description = getDocblock(programPath);
}
}
documentation.setDescription(description || '');
} }
module.exports = componentDocblockHandler; module.exports = componentDocblockHandler;

View File

@ -0,0 +1,79 @@
/*
* 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 getPropertyName = require('../utils/getPropertyName');
var getPropertyValuePath = require('../utils/getPropertyValuePath');
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 defaultPropsHandler(documentation: Documentation, path: NodePath) {
var getDefaultPropsPath = getPropertyValuePath(path, 'getDefaultProps');
if (!getDefaultPropsPath ||
!types.FunctionExpression.check(getDefaultPropsPath.node)) {
return;
}
// Find the value that is returned from the function and process it if it is
// an object literal.
var objectExpressionPath;
visit(getDefaultPropsPath.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 = defaultPropsHandler;

View File

@ -0,0 +1,16 @@
/*
* 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";
exports.componentDocblockHandler = require('./componentDocblockHandler');
exports.defaultPropsHandler = require('./defaultPropsHandler');
exports.propTypeHandler = require('./propTypeHandler');
exports.propDocBlockHandler = require('./propDocBlockHandler');

View File

@ -18,13 +18,15 @@ var Documentation = require('../Documentation');
var types = require('recast').types.namedTypes; var types = require('recast').types.namedTypes;
var getDocblock = require('../utils/docblock').getDocblock; var getDocblock = require('../utils/docblock').getDocblock;
var getPropertyName = require('../utils/getPropertyName'); var getPropertyName = require('../utils/getPropertyName');
var getPropertyValuePath = require('../utils/getPropertyValuePath');
function propDocBlockHandler(documentation: Documentation, path: NodePath) { function propDocBlockHandler(documentation: Documentation, path: NodePath) {
if (!types.ObjectExpression.check(path.node)) { var propTypesPath = getPropertyValuePath(path, 'propTypes');
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
return; return;
} }
path.get('properties').each(function(propertyPath) { propTypesPath.get('properties').each(function(propertyPath) {
// we only support documentation of actual properties, not spread // we only support documentation of actual properties, not spread
if (types.Property.check(propertyPath.node)) { if (types.Property.check(propertyPath.node)) {
var propDescriptor = documentation.getPropDescriptor( var propDescriptor = documentation.getPropDescriptor(

View File

@ -15,27 +15,17 @@
var Documentation = require('../Documentation'); var Documentation = require('../Documentation');
var expressionTo = require('../utils/expressionTo'); var getMembers = require('../utils/getMembers');
var getNameOrValue = require('../utils/getNameOrValue'); var getNameOrValue = require('../utils/getNameOrValue');
var getPropType = require('../utils/getPropType');
var getPropertyName = require('../utils/getPropertyName'); var getPropertyName = require('../utils/getPropertyName');
var getPropertyValuePath = require('../utils/getPropertyValuePath');
var isReactModuleName = require('../utils/isReactModuleName'); var isReactModuleName = require('../utils/isReactModuleName');
var recast = require('recast'); var recast = require('recast');
var resolveToModule = require('../utils/resolveToModule'); var resolveToModule = require('../utils/resolveToModule');
var resolveToValue = require('../utils/resolveToValue'); var resolveToValue = require('../utils/resolveToValue');
var types = recast.types.namedTypes; var types = recast.types.namedTypes;
var simplePropTypes = {
array: 1,
bool: 1,
func: 1,
number: 1,
object: 1,
string: 1,
any: 1,
element: 1,
node: 1
};
function isPropTypesExpression(path) { function isPropTypesExpression(path) {
var moduleName = resolveToModule(path); var moduleName = resolveToModule(path);
if (moduleName) { if (moduleName) {
@ -44,154 +34,14 @@ function isPropTypesExpression(path) {
return false; return false;
} }
function getEnumValues(path) {
return path.get('elements').map(function(elementPath) {
return {
value: expressionTo.String(elementPath),
computed: !types.Literal.check(elementPath.node)
};
});
}
function getPropTypeOneOf(path) {
types.CallExpression.assert(path.node);
var argumentPath = path.get('arguments', 0);
var type = {name: 'enum'};
if (!types.ArrayExpression.check(argumentPath.node)) {
type.computed = true;
type.value = expressionTo.String(argumentPath);
} else {
type.value = getEnumValues(argumentPath);
}
return type;
}
function getPropTypeOneOfType(path) {
types.CallExpression.assert(path.node);
var argumentPath = path.get('arguments', 0);
var type = {name: 'union'};
if (!types.ArrayExpression.check(argumentPath.node)) {
type.computed = true;
type.value = expressionTo.String(argumentPath);
} else {
type.value = argumentPath.get('elements').map(getPropType);
}
return type;
}
function getPropTypeArrayOf(path) {
types.CallExpression.assert(path.node);
var argumentPath = path.get('arguments', 0);
var type = {name: 'arrayof'};
var subType = getPropType(argumentPath);
if (subType.name === 'unknown') {
type.value = expressionTo.String(argumentPath);
type.computed = true;
} else {
type.value = subType;
}
return type;
}
function getPropTypeShape(path) {
types.CallExpression.assert(path.node);
var valuePath = path.get('arguments', 0);
var type: {name: string; value: any;} = {name: 'shape', value: 'unkown'};
if (!types.ObjectExpression.check(valuePath.node)) {
valuePath = resolveToValue(valuePath);
}
if (types.ObjectExpression.check(valuePath.node)) {
type.value = {};
valuePath.get('properties').each(function(propertyPath) {
type.value[getPropertyName(propertyPath)] =
getPropType(propertyPath.get('value'));
});
}
return type;
}
function getPropTypeInstanceOf(path) {
types.CallExpression.assert(path.node);
return {
name: 'instance',
value: expressionTo.String(path.get('arguments', 0))
};
}
var propTypes = {
oneOf: getPropTypeOneOf,
oneOfType: getPropTypeOneOfType,
instanceOf: getPropTypeInstanceOf,
arrayOf: getPropTypeArrayOf,
shape: getPropTypeShape
};
/**
* Tries to identify the prop type by the following rules:
*
* Member expressions which resolve to the `React` or `ReactPropTypes` module
* are inspected to see whether their properties are prop types. Strictly
* speaking we'd have to test whether the Member expression resolves to
* require('React').PropTypes, but we are not doing this right now for
* simplicity.
*
* Everything else is treated as custom validator
*/
function getPropType(path) {
var node = path.node;
if (types.Function.check(node) || !isPropTypesExpression(path)) {
return {
name: 'custom',
raw: recast.print(path).code
};
}
var expressionParts = [];
if (types.MemberExpression.check(node)) {
// React.PropTypes.something.isRequired
if (isRequired(path)) {
path = path.get('object');
node = path.node;
}
// React.PropTypes.something
expressionParts = expressionTo.Array(path);
}
if (types.CallExpression.check(node)) {
// React.PropTypes.something()
expressionParts = expressionTo.Array(path.get('callee'));
}
// React.PropTypes.something -> something
var propType = expressionParts.pop();
var type;
if (propType in propTypes) {
type = propTypes[propType](path);
} else {
type = {name: (propType in simplePropTypes) ? propType : 'unknown'};
}
return type;
}
/** /**
* Returns true of the prop is required, according to its type defintion * Returns true of the prop is required, according to its type defintion
*/ */
function isRequired(path) { function isRequired(path) {
if (types.MemberExpression.check(path.node)) { return getMembers(path).some(
var expressionParts = expressionTo.Array(path); member => !member.computed && member.path.node.name === 'isRequired' ||
if (expressionParts[expressionParts.length - 1] === 'isRequired') { member.computed && member.path.node.value === 'isRequired'
return true; );
}
}
return false;
} }
/** /**
@ -220,14 +70,18 @@ function amendPropTypes(documentation, path) {
path.get('properties').each(function(propertyPath) { path.get('properties').each(function(propertyPath) {
switch (propertyPath.node.type) { switch (propertyPath.node.type) {
case types.Property.name: case types.Property.name:
var type = getPropType(propertyPath.get('value')); var propDescriptor = documentation.getPropDescriptor(
getPropertyName(propertyPath)
);
var valuePath = propertyPath.get('value');
var type = isPropTypesExpression(valuePath) ?
getPropType(valuePath) :
{name: 'custom', raw: recast.print(valuePath).code};
if (type) { if (type) {
var propDescriptor = documentation.getPropDescriptor(
getPropertyName(propertyPath)
);
propDescriptor.type = type; propDescriptor.type = type;
propDescriptor.required = type.name !== 'custom' && propDescriptor.required =
isRequired(propertyPath.get('value')); type.name !== 'custom' && isRequired(valuePath);
} }
break; break;
case types.SpreadProperty.name: case types.SpreadProperty.name:
@ -246,13 +100,17 @@ function amendPropTypes(documentation, path) {
} }
function propTypeHandler(documentation: Documentation, path: NodePath) { function propTypeHandler(documentation: Documentation, path: NodePath) {
path = resolveToValue(path); var propTypesPath = getPropertyValuePath(resolveToValue(path), 'propTypes');
switch (path.node.type) { if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
return;
}
switch (propTypesPath.node.type) {
case types.ObjectExpression.name: case types.ObjectExpression.name:
amendPropTypes(documentation, path); amendPropTypes(documentation, propTypesPath);
break; break;
case types.MemberExpression.name: case types.MemberExpression.name:
amendComposes(documentation, path); amendComposes(documentation, propTypesPath);
} }
} }

View File

@ -13,27 +13,49 @@
*/ */
"use strict"; "use strict";
var handlers = require('./handlers');
var parse = require('./parse');
var resolver = require('./resolver');
var utils = require('./utils');
var defaultResolver = resolver.findExportedReactCreateClassCall;
var defaultHandlers = [
handlers.propTypeHandler,
handlers.propDocBlockHandler,
handlers.defaultPropsHandler,
handlers.componentDocblockHandler,
];
/** /**
* Extractor for React documentation in JavaScript. * See `lib/parse.js` for more information about the arguments. This function
* simply sets default values for convenience.
*
* The default resolver looks for *exported* `React.createClass(def)` calls
* and expected `def` to resolve to an object expression.
*
* The default `handlers` look for `propTypes` and `getDefaultProps` in the
* provided object expression, and extract prop type information, prop
* documentation (from docblocks), default prop values and component
* documentation (from a docblock).
*/ */
var ReactDocumentationParser = require('./ReactDocumentationParser'); function defaultParse(
var parser = new ReactDocumentationParser(); src: string,
resolver?: ?Resolver,
handlers?: ?Array<Handler>
): (Array<Object>|Object) {
if (!resolver) {
resolver = defaultResolver;
}
if (!handlers) {
handlers = defaultHandlers;
}
parser.addHandler( return parse(src, resolver, handlers);
require('./handlers/propTypeHandler'), }
'propTypes'
);
parser.addHandler(
require('./handlers/propDocBlockHandler'),
'propTypes'
);
parser.addHandler(
require('./handlers/defaultValueHandler'),
'getDefaultProps'
);
parser.addHandler( module.exports = {
require('./handlers/componentDocblockHandler') parse: defaultParse,
); handlers,
resolver,
module.exports = parser; utils
};

69
website/react-docgen/lib/parse.js vendored Normal file
View File

@ -0,0 +1,69 @@
/*
* 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 recast = require('recast');
var ERROR_MISSING_DEFINITION = 'No suitable component definition found.';
function executeHandlers(handlers, componentDefinitions) {
return componentDefinitions.map(componentDefinition => {
var documentation = new Documentation();
handlers.forEach(handler => handler(documentation, componentDefinition));
return documentation.toObject();
});
}
/**
* Takes JavaScript source code and returns an object with the information
* extract from it.
*
* `resolver` is a strategy to find the AST node(s) of the component
* definition(s) inside `src`.
* 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.
*
* `handlers` is an array of functions which are passed a reference to the
* component definitions (extracted by `resolver`) so that they can extract
* information from it. They get also passed a reference to a `Documentation`
* object to attach the information to.
*
* If `resolver` returns an array of component definitions, `parse` will return
* an array of documentation objects. If `resolver` returns a single node
* instead, `parse` will return a documentation object.
*/
function parse(
src: string,
resolver: Resolver,
handlers: Array<Handler>
): (Array<Object>|Object) {
var ast = recast.parse(src);
var componentDefinitions = resolver(ast.program, recast);
var isArray = Array.isArray(componentDefinitions);
if (!componentDefinitions || (isArray && componentDefinitions.length === 0)) {
throw new Error(ERROR_MISSING_DEFINITION);
}
return isArray ?
executeHandlers(handlers, componentDefinitions) :
executeHandlers(handlers, [componentDefinitions])[0];
}
module.exports = parse;
exports.ERROR_MISSING_DEFINITION = ERROR_MISSING_DEFINITION;

View File

@ -0,0 +1,106 @@
/*
* 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);
});
});

View File

@ -0,0 +1,106 @@
/*
* 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();
});
});

View File

@ -0,0 +1,47 @@
/*
* 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;

View File

@ -0,0 +1,78 @@
/*
* 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;

View File

@ -0,0 +1,16 @@
/*
* 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";
exports.findAllReactCreateClassCalls =
require('./findAllReactCreateClassCalls');
exports.findExportedReactCreateClassCall =
require('./findExportedReactCreateClassCall');

View File

@ -0,0 +1,51 @@
/*
* 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('getMembers', function() {
var recast;
var getMembers;
var memberExpressionPath;
function parse(src) {
return new recast.types.NodePath(
recast.parse(src).program.body[0].expression
);
}
beforeEach(function() {
getMembers = require('../getMembers');
recast = require('recast');
memberExpressionPath = parse('foo.bar(123)(456)[baz][42]');
});
it('finds all "members" "inside" a MemberExpression', function() {
var b = recast.types.builders;
var members = getMembers(memberExpressionPath);
//bar(123)
expect(members[0].path.node.name).toEqual('bar');
expect(members[0].computed).toBe(false);
expect(members[0].argumentsPath.get(0).node.value).toEqual(123);
//[baz]
expect(members[1].path.node.name).toEqual('baz');
expect(members[1].computed).toBe(true);
expect(members[1].argumentsPath).toBe(null);
//[42]
expect(members[2].path.node.value).toEqual(42);
expect(members[2].computed).toBe(true);
expect(members[2].argumentsPath).toBe(null);
});
});

View File

@ -0,0 +1,144 @@
/*
* 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('getPropType', function() {
var utils;
var getPropType;
function parse(src) {
return utils.parse(src).get('body', 0, 'expression');
}
beforeEach(function() {
getPropType = require('../getPropType');
utils = require('../../../tests/utils');
});
it('detects simple prop types', function() {
var simplePropTypes = [
'array',
'bool',
'func',
'number',
'object',
'string',
'any',
'element',
'node',
];
simplePropTypes.forEach(
type => expect(getPropType(parse('React.PropTypes.' + type)))
.toEqual({name: type})
);
// It doesn't actually matter what the MemberExpression is
simplePropTypes.forEach(
type => expect(getPropType(parse('Foo.' + type + '.bar')))
.toEqual({name: type})
);
// Doesn't even have to be a MemberExpression
simplePropTypes.forEach(
type => expect(getPropType(parse(type)))
.toEqual({name: type})
);
});
it('detects complex prop types', function() {
expect(getPropType(parse('oneOf(["foo", "bar"])'))).toEqual({
name: 'enum',
value: [
{value: '"foo"', computed: false},
{value: '"bar"', computed: false}
]
});
expect(getPropType(parse('oneOfType([number, bool])'))).toEqual({
name: 'union',
value: [
{name: 'number'},
{name: 'bool'}
]
});
// custom type
expect(getPropType(parse('oneOfType([foo])'))).toEqual({
name: 'union',
value: [{name: 'custom', raw: 'foo'}]
});
// custom type
expect(getPropType(parse('instanceOf(Foo)'))).toEqual({
name: 'instanceOf',
value: 'Foo'
});
expect(getPropType(parse('arrayOf(string)'))).toEqual({
name: 'arrayOf',
value: {name: 'string'}
});
expect(getPropType(parse('shape({foo: string, bar: bool})'))).toEqual({
name: 'shape',
value: {
foo: {
name: 'string'
},
bar: {
name: 'bool'
}
}
});
// custom
expect(getPropType(parse('shape({foo: xyz})'))).toEqual({
name: 'shape',
value: {
foo: {
name: 'custom',
raw: 'xyz'
}
}
});
});
it('resolves variables to their values', function() {
var src = [
'var shape = {bar: PropTypes.string};',
'PropTypes.shape(shape);',
].join('\n');
var propTypeExpression = utils.parse(src).get('body', 1, 'expression');
expect(getPropType(propTypeExpression)).toEqual({
name: 'shape',
value: {
bar: {name: 'string'}
}
});
});
it('detects custom validation functions', function() {
expect(getPropType(parse('(function() {})'))).toEqual({
name: 'custom',
raw: '(function() {})'
});
expect(getPropType(parse('() => {}'))).toEqual({
name: 'custom',
raw: '() => {}'
});
});
});

View File

@ -0,0 +1,42 @@
/*
* 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('getPropertyValuePath', function() {
var recast;
var getPropertyValuePath;
function parse(src) {
return new recast.types.NodePath(
recast.parse(src).program.body[0]
);
}
beforeEach(function() {
getPropertyValuePath = require('../getPropertyValuePath');
recast = require('recast');
});
it('returns the value path if the property exists', function() {
var objectExpressionPath = parse('({foo: 21, bar: 42})').get('expression');
expect(getPropertyValuePath(objectExpressionPath, 'bar'))
.toBe(objectExpressionPath.get('properties', 1).get('value'));
});
it('returns undefined if the property does not exist', function() {
var objectExpressionPath = parse('({foo: 21, bar: 42})').get('expression');
expect(getPropertyValuePath(objectExpressionPath, 'baz'))
.toBeUndefined();
});
});

View File

@ -1,3 +1,13 @@
/*
* 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"; "use strict";
jest.autoMockOff(); jest.autoMockOff();

View File

@ -0,0 +1,41 @@
/*
* 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('match', function() {
var match;
beforeEach(function() {
match = require('../match');
});
it('matches with exact properties', function() {
expect(match({foo: {bar: 42}}, {foo: {bar: 42}})).toBe(true);
});
it('matches a subset of properties in the target', function() {
expect(match({foo: {bar: 42, baz: "xyz"}}, {foo: {bar: 42}})).toBe(true);
});
it('does not match if properties are different/missing', function() {
expect(match(
{foo: {bar: 42, baz: "xyz"}},
{foo: {bar: 21, baz: "xyz"}}
)).toBe(false);
expect(match(
{foo: {baz: "xyz"}},
{foo: {bar: 21, baz: "xyz"}}
)).toBe(false);
});
});

View File

@ -0,0 +1,62 @@
/*
* 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.
*
*/
/**
* Helper methods for dealing with MemberExpressions (and CallExpressions).
* @flow
*/
"use strict";
type MemberDescriptor = {
path: NodePath;
computed: boolean;
argumentsPath?: ?NodePath;
}
var types = require('recast').types.namedTypes;
/**
* Given a "nested" Member/CallExpression, e.g.
*
* foo.bar()[baz][42]
*
* this returns a list of "members". In this example it would be something like
* [
* {path: NodePath<bar>, arguments: NodePath<empty>, computed: false},
* {path: NodePath<baz>, arguments: null, computed: true},
* {path: NodePath<42>, arguments: null, computed: false}
* ]
*/
function getMembers(path: NodePath): Array<MemberExpression> {
var result = [];
var argumentsPath = null;
loop: while(true) {
switch (true) {
case types.MemberExpression.check(path.node):
result.push({
path: path.get('property'),
computed: path.node.computed,
argumentsPath: argumentsPath,
});
argumentsPath = null;
path = path.get('object');
break;
case types.CallExpression.check(path.node):
argumentsPath = path.get('arguments');
path = path.get('callee');
break;
default:
break loop;
}
}
return result.reverse();
}
module.exports = getMembers;

View File

@ -0,0 +1,153 @@
/*
* 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 getMembers = require('./getMembers');
var getPropertyName = require('./getPropertyName');
var recast = require('recast');
var resolveToValue = require('./resolveToValue');
var types = recast.types.namedTypes;
function getEnumValues(path) {
return path.get('elements').map(function(elementPath) {
return {
value: recast.print(elementPath).code,
computed: !types.Literal.check(elementPath.node)
};
});
}
function getPropTypeOneOf(argumentPath) {
var type = {name: 'enum'};
if (!types.ArrayExpression.check(argumentPath.node)) {
type.computed = true;
type.value = recast.print(argumentPath).code;
} else {
type.value = getEnumValues(argumentPath);
}
return type;
}
function getPropTypeOneOfType(argumentPath) {
var type = {name: 'union'};
if (!types.ArrayExpression.check(argumentPath.node)) {
type.computed = true;
type.value = recast.print(argumentPath).code;
} else {
type.value = argumentPath.get('elements').map(getPropType);
}
return type;
}
function getPropTypeArrayOf(argumentPath) {
var type = {name: 'arrayOf'};
var subType = getPropType(argumentPath);
if (subType.name === 'unknown') {
type.value = recast.print(argumentPath).code;
type.computed = true;
} else {
type.value = subType;
}
return type;
}
function getPropTypeShape(argumentPath) {
var type: {name: string; value: any;} = {name: 'shape', value: 'unkown'};
if (!types.ObjectExpression.check(argumentPath.node)) {
argumentPath = resolveToValue(argumentPath);
}
if (types.ObjectExpression.check(argumentPath.node)) {
type.value = {};
argumentPath.get('properties').each(function(propertyPath) {
type.value[getPropertyName(propertyPath)] =
getPropType(propertyPath.get('value'));
});
}
return type;
}
function getPropTypeInstanceOf(argumentPath) {
return {
name: 'instanceOf',
value: recast.print(argumentPath).code
};
}
var simplePropTypes = {
array: 1,
bool: 1,
func: 1,
number: 1,
object: 1,
string: 1,
any: 1,
element: 1,
node: 1
};
var propTypes = {
oneOf: getPropTypeOneOf,
oneOfType: getPropTypeOneOfType,
instanceOf: getPropTypeInstanceOf,
arrayOf: getPropTypeArrayOf,
shape: getPropTypeShape
};
/**
* Tries to identify the prop type by inspecting the path for known
* prop type names. This method doesn't check whether the found type is actually
* from React.PropTypes. It simply assumes that a match has the same meaning
* as the React.PropTypes one.
*
* If there is no match, "custom" is returned.
*/
function getPropType(path: NodePath): PropTypeDescriptor {
var node = path.node;
var descriptor;
getMembers(path).some(member => {
var node = member.path.node;
var name;
if (types.Literal.check(node)) {
name = node.value;
} else if (types.Identifier.check(node) && !member.computed) {
name = node.name;
}
if (simplePropTypes.hasOwnProperty(name)) {
descriptor = {name};
return true;
} else if (propTypes.hasOwnProperty(name) && member.argumentsPath) {
descriptor = propTypes[name](member.argumentsPath.get(0));
return true;
}
});
if (!descriptor) {
if (types.Identifier.check(node) &&
simplePropTypes.hasOwnProperty(node.name)) {
descriptor = {name: node.name};
} else if (types.CallExpression.check(node) &&
types.Identifier.check(node.callee) &&
propTypes.hasOwnProperty(node.callee.name)) {
descriptor = propTypes[node.callee.name](path.get('arguments', 0));
} else {
descriptor = {name: 'custom', raw: recast.print(path).code};
}
}
return descriptor;
}
module.exports = getPropType;

View File

@ -0,0 +1,31 @@
/*
* 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 types = require('recast').types.namedTypes;
var getPropertyName = require('./getPropertyName');
/**
* Given an ObjectExpression, this function returns the path of the value of
* the property with name `propertyName`.
*/
function getPropertyValuePath(path: NodePath, propertyName: string): ?NodePath {
types.ObjectExpression.assert(path.node);
return path.get('properties')
.filter(propertyPath => getPropertyName(propertyPath) === propertyName)
.map(propertyPath => propertyPath.get('value'))[0];
}
module.exports = getPropertyValuePath;

23
website/react-docgen/lib/utils/index.js vendored Normal file
View File

@ -0,0 +1,23 @@
/*
* 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";
exports.docblock = require('./docblock');
exports.getMembers = require('./getMembers');
exports.getPropType = require('./getPropType');
exports.getPropertyName = require('./getPropertyName');
exports.getPropertyValuePath = require('./getPropertyValuePath');
exports.isExportsOrModuleAssignment = require('./isExportsOrModuleAssignment');
exports.isReactCreateClassCall = require('./isReactCreateClassCall');
exports.isReactModuleName = require('./isReactModuleName');
exports.match = require('./match');
exports.resolveToModule = require('./resolveToModule');
exports.resolveToValue = require('./resolveToValue');

View File

@ -30,7 +30,7 @@ function match(node: ASTNOde, pattern: Object): boolean {
if (!match(node[prop], pattern[prop])) { if (!match(node[prop], pattern[prop])) {
return false; return false;
} }
} else if (pattern[prop] !== pattern[prop]) { } else if (node[prop] !== pattern[prop]) {
return false; return false;
} }
} }

View File

@ -29,7 +29,8 @@
"react-tools": "^0.12.2" "react-tools": "^0.12.2"
}, },
"jest": { "jest": {
"scriptPreprocessor": "./preprocessor", "scriptPreprocessor": "./tests/preprocessor",
"testPathDirs": ["lib"] "testPathDirs": ["lib"],
"unmockedModulePathPatterns": ["tests/utils"]
} }
} }

View File

@ -0,0 +1,9 @@
"use strict";
var reactTools = require('react-tools');
function process(source) {
return reactTools.transform(source, {harmony: true, stripTypes: true});
}
exports.process = process;

49
website/react-docgen/tests/utils.js vendored Normal file
View File

@ -0,0 +1,49 @@
"use strict";
/**
* Helper methods for tests.
*/
var recast = require.requireActual('recast');
function stringify(value) {
if (Array.isArray(value)) {
return value.join('\n');
}
return value;
}
/**
* Returns a NodePath to the program node of the passed node
*/
function parse(src) {
return new recast.types.NodePath(recast.parse(stringify(src)).program);
}
/**
* Injects src into template by replacing the occurrence of %s.
*/
function parseWithTemplate(src, template) {
return parse(template.replace('%s', stringify(src)));
}
/**
* Default template that simply defines React and PropTypes.
*/
var REACT_TEMPLATE = [
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'%s;',
].join('\n');
var MODULE_TEMPLATE = [
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'var Component = React.createClass(%s);',
'module.exports = Component'
].join('\n');
exports.parse = parse;
exports.parseWithTemplate = parseWithTemplate;
exports.REACT_TEMPLATE = REACT_TEMPLATE;
exports.MODULE_TEMPLATE = MODULE_TEMPLATE;

View File

@ -1,10 +1,4 @@
var docs = require('../react-docgen'); var docs = require('../react-docgen');
var findExportedReactCreateClassCall = require(
'../react-docgen/dist/strategies/findExportedReactCreateClassCall'
);
var findAllReactCreateClassCalls = require(
'../react-docgen/dist/strategies/findAllReactCreateClassCalls'
);
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var slugify = require('../core/slugify'); var slugify = require('../core/slugify');
@ -18,11 +12,11 @@ function getNameFromPath(filepath) {
} }
function docsToMarkdown(filepath, i) { function docsToMarkdown(filepath, i) {
var json = docs.parseSource( var json = docs.parse(
fs.readFileSync(filepath), fs.readFileSync(filepath),
function(node, recast) { function(node, recast) {
return findExportedReactCreateClassCall(node, recast) || return docs.resolver.findExportedReactCreateClassCall(node, recast) ||
findAllReactCreateClassCalls(node, recast)[0]; docs.resolver.findAllReactCreateClassCalls(node, recast)[0];
} }
) )
@ -50,11 +44,11 @@ function docsToMarkdown(filepath, i) {
var components = [ var components = [
'../Libraries/Components/Navigation/NavigatorIOS.ios.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js',
'../Libraries/Components/Image/Image.ios.js', '../Libraries/Image/Image.ios.js',
'../Libraries/Components/ListView/ListView.js', '../Libraries/Components/ListView/ListView.js',
'../Libraries/Components/Navigation/NavigatorIOS.ios.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js',
'../Libraries/Components/ScrollView/ScrollView.ios.js', '../Libraries/Components/ScrollView/ScrollView.ios.js',
'../Libraries/Components/Text/Text.js', '../Libraries/Text/Text.js',
'../Libraries/Components/TextInput/TextInput.ios.js', '../Libraries/Components/TextInput/TextInput.ios.js',
'../Libraries/Components/Touchable/TouchableHighlight.js', '../Libraries/Components/Touchable/TouchableHighlight.js',
'../Libraries/Components/Touchable/TouchableWithoutFeedback.js', '../Libraries/Components/Touchable/TouchableWithoutFeedback.js',