Update react-docgen
This commit is contained in:
parent
7b51a09444
commit
f77d9f8bae
|
@ -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.
|
|
@ -1,15 +1,10 @@
|
|||
# react-docgen
|
||||
|
||||
`react-docgen` extracts information from React components with which
|
||||
you can generate documentation for those components.
|
||||
`react-docgen` is a CLI and toolbox to help extracting information from React components, and generate documentation from it.
|
||||
|
||||
It uses [recast][] to parse the provided files into an AST, looks for React
|
||||
component definitions, and inspects the `propTypes` and `getDefaultProps`
|
||||
declarations. The output is a JSON blob with the extracted information.
|
||||
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.
|
||||
|
||||
Note that component definitions must follow certain guidelines in order to be
|
||||
analyzable by this tool. We will work towards less strict guidelines, but there
|
||||
is a limit to what is statically analyzable.
|
||||
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
|
||||
|
||||
|
@ -41,22 +36,62 @@ Extract meta information from React components.
|
|||
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
|
||||
|
||||
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
|
||||
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
|
||||
analyzed.
|
||||
- The component definition must be an object literal.
|
||||
- `propTypes` must be an object literal or resolve to an object literal in the
|
||||
same file.
|
||||
- The `return` statement in `getDefaultProps` must consist of an object literal.
|
||||
- The `return` statement in `getDefaultProps` must contain an object literal.
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ if (paths.length === 0) {
|
|||
source += chunk;
|
||||
});
|
||||
process.stdin.on('end', function () {
|
||||
exitWithResult(parser.parseSource(source));
|
||||
exitWithResult(parser.parse(source));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ function traverseDir(path, result, done) {
|
|||
exitWithError(error);
|
||||
}
|
||||
try {
|
||||
result[filename] = parser.parseSource(content);
|
||||
result[filename] = parser.parse(content);
|
||||
} catch(error) {
|
||||
writeError(error, path);
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ async.eachSeries(paths, function(path, done) {
|
|||
}
|
||||
else {
|
||||
try {
|
||||
result[path] = parser.parseSource(fs.readFileSync(path));
|
||||
result[path] = parser.parse(fs.readFileSync(path));
|
||||
} catch(error) {
|
||||
writeError(error, path);
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -28,3 +28,8 @@ declare class NodePath {
|
|||
each(f: (p: NodePath) => void): void;
|
||||
map<T>(f: (p: NodePath) => T): Array<T>;
|
||||
}
|
||||
|
||||
type Recast = {
|
||||
parse: (src: string) => ASTNode;
|
||||
print: (path: NodePath) => {code: string};
|
||||
};
|
||||
|
|
|
@ -13,17 +13,6 @@
|
|||
*/
|
||||
"use strict";
|
||||
|
||||
type PropDescriptor = {
|
||||
type?: {
|
||||
name: string;
|
||||
value?: any;
|
||||
raw?: string;
|
||||
};
|
||||
required?: boolean;
|
||||
defaultValue?: any;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
class Documentation {
|
||||
_props: Object;
|
||||
_description: string;
|
||||
|
|
|
@ -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;
|
|
@ -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: {}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
|
@ -11,86 +11,74 @@
|
|||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
jest.mock('../../Documentation');
|
||||
|
||||
describe('React documentation parser', function() {
|
||||
var parser;
|
||||
describe('componentDocblockHandler', function() {
|
||||
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() {
|
||||
parser = new (require('../../ReactDocumentationParser'));
|
||||
parser.addHandler(require('../componentDocblockHandler'));
|
||||
utils = require('../../../tests/utils');
|
||||
documentation = new (require('../../Documentation'));
|
||||
componentDocblockHandler = require('../componentDocblockHandler');
|
||||
});
|
||||
|
||||
it('finds docblocks for component definitions', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
var definition = parse([
|
||||
'/**',
|
||||
' * Component description',
|
||||
' */',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
props: {},
|
||||
description: 'Component description'
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
componentDocblockHandler(documentation, definition);
|
||||
expect(documentation.description).toBe('Component description');
|
||||
});
|
||||
|
||||
it('ignores other types of comments', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
var definition = parse([
|
||||
'/*',
|
||||
' * This is not a docblock',
|
||||
' */',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
props: {},
|
||||
description: ''
|
||||
};
|
||||
componentDocblockHandler(documentation, definition);
|
||||
expect(documentation.description).toBe('');
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
|
||||
|
||||
source = [
|
||||
'var React = require("React");',
|
||||
definition = parse([
|
||||
'// Inline comment',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
].join('\n'));
|
||||
|
||||
expectedResult = {
|
||||
props: {},
|
||||
description: ''
|
||||
};
|
||||
|
||||
result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
componentDocblockHandler(documentation, definition);
|
||||
expect(documentation.description).toBe('');
|
||||
});
|
||||
|
||||
it('only considers the docblock directly above the definition', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
var definition = parse([
|
||||
'/**',
|
||||
' * This is the wrong docblock',
|
||||
' */',
|
||||
'var something_else = "foo";',
|
||||
'var Component = React.createClass({});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
props: {},
|
||||
description: ''
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
componentDocblockHandler(documentation, definition);
|
||||
expect(documentation.description).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,28 +11,25 @@
|
|||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
jest.mock('../../Documentation');
|
||||
|
||||
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;
|
||||
describe('propDocblockHandler', function() {
|
||||
var utils;
|
||||
var documentation;
|
||||
var propDocblockHandler;
|
||||
|
||||
beforeEach(function() {
|
||||
parser = new (require('../../ReactDocumentationParser'));
|
||||
parser.addHandler(require('../propDocblockHandler'), 'propTypes');
|
||||
utils = require('../../../tests/utils');
|
||||
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() {
|
||||
var source = getSource([
|
||||
var definition = parse([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
|
@ -48,24 +45,19 @@ describe('React documentation parser', function() {
|
|||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: 'Bar comment'
|
||||
}
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: 'Bar comment'
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
it('can handle multline comments', function() {
|
||||
var source = getSource([
|
||||
var definition = parse([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
|
@ -79,22 +71,17 @@ describe('React documentation parser', function() {
|
|||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
description:
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description:
|
||||
'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() {
|
||||
var source = getSource([
|
||||
var definition = parse([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
|
@ -112,24 +99,19 @@ describe('React documentation parser', function() {
|
|||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: 'Bar comment'
|
||||
}
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: 'Bar comment'
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
it('only considers the comment with the property below it', function() {
|
||||
var source = getSource([
|
||||
var definition = parse([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' /**',
|
||||
|
@ -141,24 +123,19 @@ describe('React documentation parser', function() {
|
|||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: ''
|
||||
}
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
it('understands and ignores the spread operator', function() {
|
||||
var source = getSource([
|
||||
var definition = parse([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' ...Foo.propTypes,',
|
||||
|
@ -166,24 +143,15 @@ describe('React documentation parser', function() {
|
|||
' * Foo comment',
|
||||
' */',
|
||||
' foo: Prop.bool,',
|
||||
' bar: Prop.bool,',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
},
|
||||
bar: {
|
||||
description: ''
|
||||
}
|
||||
propDocblockHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
description: 'Foo comment'
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,464 +11,164 @@
|
|||
"use strict";
|
||||
|
||||
jest.autoMockOff();
|
||||
jest.mock('../../Documentation');
|
||||
|
||||
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;
|
||||
describe('propTypeHandler', function() {
|
||||
var utils;
|
||||
var getPropTypeMock;
|
||||
var documentation;
|
||||
var propTypeHandler;
|
||||
|
||||
beforeEach(function() {
|
||||
parser = new (require('../../ReactDocumentationParser'));
|
||||
parser.addHandler(require('../propTypeHandler'), 'propTypes');
|
||||
utils = require('../../../tests/utils');
|
||||
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() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var Prop = React.PropTypes;',
|
||||
'var Prop1 = require("React").PropTypes;',
|
||||
'var Component = React.createClass({',
|
||||
var definition = parse([
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' foo: Prop.bool,',
|
||||
' bar: Prop1.bool,',
|
||||
' foo: PropTypes.bool,',
|
||||
' bar: require("react").PropTypes.bool,',
|
||||
' }',
|
||||
'});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
'})',
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
type: {name: 'bool'},
|
||||
required: false
|
||||
},
|
||||
bar: {
|
||||
type: {name: 'bool'},
|
||||
required: false
|
||||
}
|
||||
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
bar: {
|
||||
type: {},
|
||||
required: false
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
it('finds definitions via the ReactPropTypes module', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var Prop = require("ReactPropTypes");',
|
||||
'var Component = React.createClass({',
|
||||
var definition = parse([
|
||||
'({',
|
||||
' 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'));
|
||||
|
||||
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);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('detects complex prop types', function() {
|
||||
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);
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('detects whether a prop is required', function() {
|
||||
var source = getSource([
|
||||
'{',
|
||||
var definition = parse([
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' array_prop: PropTypes.array.isRequired,',
|
||||
' bool_prop: PropTypes.bool.isRequired,',
|
||||
' 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: ',
|
||||
' simple_prop: PropTypes.array.isRequired,',
|
||||
' complex_prop: ',
|
||||
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired,',
|
||||
' instanceOf_prop: PropTypes.instanceOf(Foo).isRequired',
|
||||
' }',
|
||||
'}'
|
||||
'})'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props:{
|
||||
array_prop: {
|
||||
type: {name: 'array'},
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
simple_prop: {
|
||||
type: {},
|
||||
required: true
|
||||
},
|
||||
bool_prop: {
|
||||
type: {name: 'bool'},
|
||||
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'
|
||||
},
|
||||
complex_prop: {
|
||||
type: {},
|
||||
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() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var PropTypes = React.PropTypes;',
|
||||
'var Prop = require("Foo");',
|
||||
'var Component = React.createClass({',
|
||||
var definition = parse([
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' custom_propA: PropTypes.bool,',
|
||||
' custom_propB: Prop.bool.isRequired',
|
||||
' }',
|
||||
'});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
'})',
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
custom_propA: {
|
||||
type: {name: 'bool'},
|
||||
required: false
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
custom_propA: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
custom_propB: {
|
||||
type: {
|
||||
name: 'custom',
|
||||
raw: 'Prop.bool.isRequired'
|
||||
},
|
||||
custom_propB: {
|
||||
type: {
|
||||
name: 'custom',
|
||||
raw: 'Prop.bool.isRequired'
|
||||
},
|
||||
required: false
|
||||
}
|
||||
required: false
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
it('understands the spread operator', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var PropTypes = React.PropTypes;',
|
||||
var definition = parse([
|
||||
'var Foo = require("Foo.react");',
|
||||
'var props = {bar: PropTypes.bool};',
|
||||
'var Component = React.createClass({',
|
||||
'({',
|
||||
' propTypes: {',
|
||||
' ...Foo.propTypes,',
|
||||
' ...props,',
|
||||
' foo: PropTypes.number',
|
||||
' }',
|
||||
'});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
'})',
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
composes: ['Foo.react'],
|
||||
props:{
|
||||
foo: {
|
||||
type: {name: 'number'},
|
||||
required: false
|
||||
},
|
||||
bar: {
|
||||
type: {name: 'bool'},
|
||||
required: false
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
propTypeHandler(documentation, definition);
|
||||
expect(documentation.composes).toEqual(['Foo.react']);
|
||||
expect(documentation.descriptors).toEqual({
|
||||
foo: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
bar: {
|
||||
type: {},
|
||||
required: false
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,15 +25,26 @@ function componentDocblockHandler(
|
|||
documentation: Documentation,
|
||||
path: NodePath
|
||||
) {
|
||||
var description = '';
|
||||
// Find parent statement (e.g. var Component = React.createClass(path);)
|
||||
var description = null;
|
||||
// Find parent statement (e.g. var Component = React.createClass(<path>);)
|
||||
while (path && !n.Statement.check(path.node)) {
|
||||
path = path.parent;
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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;
|
|
@ -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');
|
|
@ -18,13 +18,15 @@ 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');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
path.get('properties').each(function(propertyPath) {
|
||||
propTypesPath.get('properties').each(function(propertyPath) {
|
||||
// we only support documentation of actual properties, not spread
|
||||
if (types.Property.check(propertyPath.node)) {
|
||||
var propDescriptor = documentation.getPropDescriptor(
|
||||
|
|
|
@ -15,27 +15,17 @@
|
|||
|
||||
var Documentation = require('../Documentation');
|
||||
|
||||
var expressionTo = require('../utils/expressionTo');
|
||||
var getMembers = require('../utils/getMembers');
|
||||
var getNameOrValue = require('../utils/getNameOrValue');
|
||||
var getPropType = require('../utils/getPropType');
|
||||
var getPropertyName = require('../utils/getPropertyName');
|
||||
var getPropertyValuePath = require('../utils/getPropertyValuePath');
|
||||
var isReactModuleName = require('../utils/isReactModuleName');
|
||||
var recast = require('recast');
|
||||
var resolveToModule = require('../utils/resolveToModule');
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
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) {
|
||||
var moduleName = resolveToModule(path);
|
||||
if (moduleName) {
|
||||
|
@ -44,154 +34,14 @@ function isPropTypesExpression(path) {
|
|||
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
|
||||
*/
|
||||
function isRequired(path) {
|
||||
if (types.MemberExpression.check(path.node)) {
|
||||
var expressionParts = expressionTo.Array(path);
|
||||
if (expressionParts[expressionParts.length - 1] === 'isRequired') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return getMembers(path).some(
|
||||
member => !member.computed && member.path.node.name === 'isRequired' ||
|
||||
member.computed && member.path.node.value === 'isRequired'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -220,14 +70,18 @@ function amendPropTypes(documentation, path) {
|
|||
path.get('properties').each(function(propertyPath) {
|
||||
switch (propertyPath.node.type) {
|
||||
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) {
|
||||
var propDescriptor = documentation.getPropDescriptor(
|
||||
getPropertyName(propertyPath)
|
||||
);
|
||||
propDescriptor.type = type;
|
||||
propDescriptor.required = type.name !== 'custom' &&
|
||||
isRequired(propertyPath.get('value'));
|
||||
propDescriptor.required =
|
||||
type.name !== 'custom' && isRequired(valuePath);
|
||||
}
|
||||
break;
|
||||
case types.SpreadProperty.name:
|
||||
|
@ -246,13 +100,17 @@ function amendPropTypes(documentation, path) {
|
|||
}
|
||||
|
||||
function propTypeHandler(documentation: Documentation, path: NodePath) {
|
||||
path = resolveToValue(path);
|
||||
switch (path.node.type) {
|
||||
var propTypesPath = getPropertyValuePath(resolveToValue(path), 'propTypes');
|
||||
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (propTypesPath.node.type) {
|
||||
case types.ObjectExpression.name:
|
||||
amendPropTypes(documentation, path);
|
||||
amendPropTypes(documentation, propTypesPath);
|
||||
break;
|
||||
case types.MemberExpression.name:
|
||||
amendComposes(documentation, path);
|
||||
amendComposes(documentation, propTypesPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,27 +13,49 @@
|
|||
*/
|
||||
"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');
|
||||
var parser = new ReactDocumentationParser();
|
||||
function defaultParse(
|
||||
src: string,
|
||||
resolver?: ?Resolver,
|
||||
handlers?: ?Array<Handler>
|
||||
): (Array<Object>|Object) {
|
||||
if (!resolver) {
|
||||
resolver = defaultResolver;
|
||||
}
|
||||
if (!handlers) {
|
||||
handlers = defaultHandlers;
|
||||
}
|
||||
|
||||
parser.addHandler(
|
||||
require('./handlers/propTypeHandler'),
|
||||
'propTypes'
|
||||
);
|
||||
parser.addHandler(
|
||||
require('./handlers/propDocBlockHandler'),
|
||||
'propTypes'
|
||||
);
|
||||
parser.addHandler(
|
||||
require('./handlers/defaultValueHandler'),
|
||||
'getDefaultProps'
|
||||
);
|
||||
return parse(src, resolver, handlers);
|
||||
}
|
||||
|
||||
parser.addHandler(
|
||||
require('./handlers/componentDocblockHandler')
|
||||
);
|
||||
|
||||
module.exports = parser;
|
||||
module.exports = {
|
||||
parse: defaultParse,
|
||||
handlers,
|
||||
resolver,
|
||||
utils
|
||||
};
|
||||
|
|
|
@ -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;
|
106
website/react-docgen/lib/resolver/__tests__/findAllReactCreateClassCalls-test.js
vendored
Normal file
106
website/react-docgen/lib/resolver/__tests__/findAllReactCreateClassCalls-test.js
vendored
Normal 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);
|
||||
});
|
||||
});
|
106
website/react-docgen/lib/resolver/__tests__/findExportedReactCreateClassCall-test.js
vendored
Normal file
106
website/react-docgen/lib/resolver/__tests__/findExportedReactCreateClassCall-test.js
vendored
Normal 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();
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
|
@ -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: '() => {}'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
|
@ -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";
|
||||
|
||||
jest.autoMockOff();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
|
@ -30,7 +30,7 @@ function match(node: ASTNOde, pattern: Object): boolean {
|
|||
if (!match(node[prop], pattern[prop])) {
|
||||
return false;
|
||||
}
|
||||
} else if (pattern[prop] !== pattern[prop]) {
|
||||
} else if (node[prop] !== pattern[prop]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
"react-tools": "^0.12.2"
|
||||
},
|
||||
"jest": {
|
||||
"scriptPreprocessor": "./preprocessor",
|
||||
"testPathDirs": ["lib"]
|
||||
"scriptPreprocessor": "./tests/preprocessor",
|
||||
"testPathDirs": ["lib"],
|
||||
"unmockedModulePathPatterns": ["tests/utils"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,10 +1,4 @@
|
|||
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 path = require('path');
|
||||
var slugify = require('../core/slugify');
|
||||
|
@ -18,11 +12,11 @@ function getNameFromPath(filepath) {
|
|||
}
|
||||
|
||||
function docsToMarkdown(filepath, i) {
|
||||
var json = docs.parseSource(
|
||||
var json = docs.parse(
|
||||
fs.readFileSync(filepath),
|
||||
function(node, recast) {
|
||||
return findExportedReactCreateClassCall(node, recast) ||
|
||||
findAllReactCreateClassCalls(node, recast)[0];
|
||||
return docs.resolver.findExportedReactCreateClassCall(node, recast) ||
|
||||
docs.resolver.findAllReactCreateClassCalls(node, recast)[0];
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -50,11 +44,11 @@ function docsToMarkdown(filepath, i) {
|
|||
|
||||
var components = [
|
||||
'../Libraries/Components/Navigation/NavigatorIOS.ios.js',
|
||||
'../Libraries/Components/Image/Image.ios.js',
|
||||
'../Libraries/Image/Image.ios.js',
|
||||
'../Libraries/Components/ListView/ListView.js',
|
||||
'../Libraries/Components/Navigation/NavigatorIOS.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/Touchable/TouchableHighlight.js',
|
||||
'../Libraries/Components/Touchable/TouchableWithoutFeedback.js',
|
||||
|
|
Loading…
Reference in New Issue