Use npm version of react-docgen

This commit is contained in:
Felix Kling 2015-03-16 15:06:57 -07:00
parent 9b95a896e9
commit 84207f2ec8
58 changed files with 11 additions and 3476 deletions

View File

@ -1,18 +1,18 @@
{
"scripts": {
"start": "node server/server.js",
"postinstall": "cd react-docgen && npm install"
"start": "node server/server.js"
},
"dependencies": {
"react": "~0.12.0",
"optimist": "0.6.0",
"react-page-middleware": "git://github.com/facebook/react-page-middleware.git",
"connect": "2.8.3",
"glob": "*",
"mkdirp": "*",
"request": "*",
"fs.extra": "*",
"esprima-fb": "*",
"jstransform": "*"
"fs.extra": "*",
"glob": "*",
"jstransform": "*",
"mkdirp": "*",
"optimist": "0.6.0",
"react": "~0.12.0",
"react-docgen": "^1.0.0",
"react-page-middleware": "git://github.com/facebook/react-page-middleware.git",
"request": "*"
}
}

View File

@ -1 +0,0 @@
dist/

View File

@ -1,43 +0,0 @@
# 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,30 +0,0 @@
BSD License
For React docs generator software
Copyright (c) 2015, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,23 +0,0 @@
Additional Grant of Patent Rights
"Software" means the React docs generator software distributed by Facebook, Inc.
Facebook hereby grants you a perpetual, worldwide, royalty-free, non-exclusive,
irrevocable (subject to the termination provision below) license under any
rights in any patent claims owned by Facebook, to make, have made, use, sell,
offer to sell, import, and otherwise transfer the Software. For avoidance of
doubt, no license is granted under Facebooks rights in any patent claims that
are infringed by (i) modifications to the Software made by you or a third party,
or (ii) the Software in combination with any software or other technology
provided by you or a third party.
The license granted hereunder will terminate, automatically and without notice,
for anyone that makes any claim (including by filing any lawsuit, assertion or
other action) alleging (a) direct, indirect, or contributory infringement or
inducement to infringe any patent: (i) by Facebook or any of its subsidiaries or
affiliates, whether or not such claim is related to the Software, (ii) by any
party if such claim arises in whole or in part from any software, product or
service of Facebook or any of its subsidiaries or affiliates, whether or not
such claim is related to the Software, or (iii) by any party relating to the
Software; or (b) that any right in any patent claim of Facebook is invalid or
unenforceable.

View File

@ -1,222 +0,0 @@
# react-docgen
`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 source into an AST and provides methods to process this AST to extract the desired information. The output / return value is a JSON blob / JavaScript object.
It provides a default implementation for React components defined via `React.createClass`. These component definitions must follow certain guidelines in order to be analyzable (see below for more info).
## Install
Install the module directly from npm:
```
npm install -g react-docgen
```
## CLI
Installing the module adds a `react-docgen` executable which allows you do convert
a single file, multiple files or an input stream. We are trying to make the
executable as versatile as possible so that it can be integrated into many
workflows.
```
Usage: react-docgen [path]... [options]
path A component file or directory. If no path is provided it reads from stdin.
Options:
-o FILE, --out FILE store extracted information in FILE
--pretty pretty print JSON
-x, --extension File extensions to consider. Repeat to define multiple extensions. Default: [js,jsx]
-i, --ignore Folders to ignore. Default: [node_modules,__tests__]
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.
Have a look at `example/` for an example of how to use the result to generate
a markdown version of the documentation.
## API
The tool can be used programmatically to extract component information and customize the extraction process:
```js
var reactDocs = require('react-docgen');
var componentInfo = reactDocs.parse(src);
```
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 contain an object literal.
## Example
For the following component
```js
var React = require('react');
/**
* General component description.
*/
var Component = React.createClass({
propTypes: {
/**
* Description of prop "foo".
*/
foo: React.PropTypes.number,
/**
* Description of prop "bar" (a custom validation function).
*/
bar: function(props, propName, componentName) {
// ...
},
baz: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]),
},
getDefaultProps: function() {
return {
foo: 42,
bar: 21
};
},
render: function() {
// ...
}
});
module.exports = Component;
```
we are getting this output:
```
{
"props": {
"foo": {
"type": {
"name": "number"
},
"required": false,
"description": "Description of prop \"foo\".",
"defaultValue": {
"value": "42",
"computed": false
}
},
"bar": {
"type": {
"name": "custom"
},
"required": false,
"description": "Description of prop \"bar\" (a custom validation function).",
"defaultValue": {
"value": "21",
"computed": false
}
},
"baz": {
"type": {
"name": "union",
"value": [
{
"name": "number"
},
{
"name": "string"
}
]
},
"required": false,
"description": ""
}
},
"description": "General component description."
}
```
## Result data structure
The structure of the JSON blob / JavaScript object is as follows:
```
{
"description": string
"props": {
"<propName>": {
"type": {
"name": "<typeName>",
["value": <typeValue>]
["raw": string]
},
"required": boolean,
"description": string,
["defaultValue": {
"value": number | string,
"computed": boolean
}]
},
...
},
["composes": <componentNames>]
}
```
(`[...]` means the property may not exist if such information was not found in the component definition)
- `<propName>`: For each prop that was found, there will be an entry in `props` under the same name.
- `<typeName>`: The name of the type, which is usually corresponds to the function name in `React.PropTypes`. However, for types define with `oneOf`, we use `"enum"` and for `oneOfType` we use `"union"`. If a custom function is provided or the type cannot be resolved to anything of `React.PropTypes`, we use `"custom"`.
- `<typeValue>`: Some types accept parameters which define the type in more detail (such as `arrayOf`, `instanceOf`, `oneOf`, etc). Those are stored in `<typeValue>`. The data type of `<typeValue>` depends on the type definition.
[recast]: https://github.com/benjamn/recast

View File

@ -1,168 +0,0 @@
#!/usr/bin/env node
/*
* 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.
*
*/
var argv = require('nomnom')
.script('react-docgen')
.help(
'Extract meta information from React components.\n' +
'If a directory is passed, it is recursively traversed.'
)
.options({
path: {
position: 0,
help: 'A component file or directory. If no path is provided it reads from stdin.',
metavar: 'PATH',
list: true
},
out: {
abbr: 'o',
help: 'store extracted information in FILE',
metavar: 'FILE'
},
pretty: {
help: 'pretty print JSON',
flag: true
},
extension: {
abbr: 'x',
help: 'File extensions to consider. Repeat to define multiple extensions. Default:',
list: true,
default: ['js', 'jsx']
},
ignoreDir: {
abbr: 'i',
full: 'ignore',
help: 'Folders to ignore. Default:',
list: true,
default: ['node_modules', '__tests__']
}
})
.parse();
var async = require('async');
var dir = require('node-dir');
var fs = require('fs');
var parser = require('../dist/main.js');
var output = argv.o;
var paths = argv.path;
var extensions = new RegExp('\\.(?:' + argv.extension.join('|') + ')$');
var ignoreDir = argv.ignoreDir;
function writeError(msg, path) {
if (path) {
process.stderr.write('Error with path "' + path + '": ');
}
process.stderr.write(msg + '\n');
}
function exitWithError(error) {
writeError(error);
process.exit(1);
}
function exitWithResult(result) {
result = argv.pretty ?
JSON.stringify(result, null, 2) :
JSON.stringify(result);
if (argv.o) {
fs.writeFileSync(argv.o, result);
} else {
process.stdout.write(result + '\n');
}
process.exit(0);
}
/**
* 1. No files passed, consume input stream
*/
if (paths.length === 0) {
var source = '';
process.stdin.setEncoding('utf8');
process.stdin.resume();
var timer = setTimeout(function() {
process.stderr.write('Still waiting for std input...');
}, 5000);
process.stdin.on('data', function (chunk) {
clearTimeout(timer);
source += chunk;
});
process.stdin.on('end', function () {
exitWithResult(parser.parse(source));
});
}
function traverseDir(path, result, done) {
dir.readFiles(
path,
{
match: extensions,
excludeDir: ignoreDir
},
function(error, content, filename, next) {
if (error) {
exitWithError(error);
}
try {
result[filename] = parser.parse(content);
} catch(error) {
writeError(error, path);
}
next();
},
function(error) {
if (error) {
writeError(error);
}
done();
}
);
}
/**
* 2. Paths are passed.
*/
var result = Object.create(null);
async.eachSeries(paths, function(path, done) {
fs.stat(path, function(error, stats) {
if (error) {
writeError(error, path);
done();
return;
}
if (stats.isDirectory()) {
traverseDir(path, result, done);
}
else {
try {
result[path] = parser.parse(fs.readFileSync(path));
} catch(error) {
writeError(error, path);
}
finally {
done();
}
}
});
}, function() {
var resultsPaths = Object.keys(result);
if (resultsPaths.length === 0) {
// we must have gotten an error
process.exit(1);
}
if (paths.length === 1) { // a single path?
fs.stat(paths[0], function(error, stats) {
exitWithResult(stats.isDirectory() ? result : result[resultsPaths[0]]);
});
} else {
exitWithResult(result);
}
});

View File

@ -1,42 +0,0 @@
#!/usr/bin/env node
/**
* This example script expects a JSON blob generated by react-docgen as input,
* e.g. react-docgen components/* | buildDocs.sh
*/
var fs = require('fs');
var generateMarkdown = require('./generateMarkdown');
var path = require('path');
var json = '';
process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
json += chunk;
}
});
process.stdin.on('end', function() {
buildDocs(JSON.parse(json));
});
function buildDocs(api) {
// api is an object keyed by filepath. We use the file name as component name.
for (var filepath in api) {
var name = getComponentName(filepath);
var markdown = generateMarkdown(name, api[filepath]);
fs.writeFileSync(name + '.md', markdown);
process.stdout.write(filepath + ' -> ' + name + '.md\n');
}
}
function getComponentName(filepath) {
var name = path.basename(filepath);
var ext;
while ((ext = path.extname(name))) {
name = name.substring(0, name.length - ext.length);
}
return name;
}

View File

@ -1,27 +0,0 @@
var React = require('react');
var Foo = require('Foo');
/**
* General component description.
*/
var Component = React.createClass({
propTypes: {
...Foo.propTypes,
/**
* Prop description
*/
bar: React.PropTypes.number
},
getDefaultProps: function() {
return {
bar: 21
};
},
render: function() {
// ...
}
});
module.exports = Component;

View File

@ -1,4 +0,0 @@
/**
* An example for a module that is not a component.
*/
module.exports = "abc";

View File

@ -1,80 +0,0 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
"use strict";
function stringOfLength(string, length) {
var newString = '';
for (var i = 0; i < length; i++) {
newString += string;
}
return newString;
}
function generateTitle(name) {
var title = '`' + name + '` (component)';
return title + '\n' + stringOfLength('=', title.length) + '\n';
}
function generateDesciption(description) {
return description + '\n';
}
function generatePropType(type) {
var values;
if (Array.isArray(type.value)) {
values = '(' +
type.value.map(function(typeValue) {
return typeValue.name || typeValue.value;
}).join('|') +
')';
} else {
values = type.value;
}
return 'type: `' + type.name + (values ? values: '') + '`\n';
}
function generatePropDefaultValue(value) {
return 'defaultValue: `' + value.value + '`\n';
}
function generateProp(propName, prop) {
return (
'### `' + propName + '`' + (prop.required ? ' (required)' : '') + '\n' +
'\n' +
(prop.description ? prop.description + '\n\n' : '') +
(prop.type ? generatePropType(prop.type) : '') +
(prop.defaultValue ? generatePropDefaultValue(prop.defaultValue) : '') +
'\n'
);
}
function generateProps(props) {
var title = 'Props';
return (
title + '\n' +
stringOfLength('-', title.length) + '\n' +
'\n' +
Object.keys(props).sort().map(function(propName) {
return generateProp(propName, props[propName]);
}).join('\n')
);
}
function generateMarkdown(name, reactAPI) {
var markdownString =
generateTitle(name) + '\n' +
generateDesciption(reactAPI.description) + '\n' +
generateProps(reactAPI.props);
return markdownString;
}
module.exports = generateMarkdown;

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
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

@ -1,35 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* A minimal set of declarations to make flow work with the recast API.
*/
type ASTNode = Object;
declare class Scope {
lookup(name: string): ?Scope;
getBindings(): Object<string, Array<NodePath>>;
}
declare class NodePath {
node: ASTNode;
parent: NodePath;
scope: Scope;
get(...x: (string|number)): 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};
};

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
class Documentation {
_props: Object;
_description: string;
_composes: Array<string>;
constructor() {
this._props = {};
this._description = '';
this._composes = [];
}
addComposes(moduleName: string) {
if (this._composes.indexOf(moduleName) === -1) {
this._composes.push(moduleName);
}
}
getDescription(): string {
return this._description;
}
setDescription(description: string): void {
this._description = description;
}
getPropDescriptor(propName: string): PropDescriptor {
var propDescriptor = this._props[propName];
if (!propDescriptor) {
propDescriptor = this._props[propName] = {};
}
return propDescriptor;
}
toObject(): Object {
var obj = {
description: this._description,
props: this._props
};
if (this._composes.length) {
obj.composes = this._composes;
}
return obj;
}
}
module.exports = Documentation;

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
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

@ -1,75 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
var 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

@ -1,52 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('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

@ -1,84 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
jest.mock('../../Documentation');
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() {
utils = require('../../../tests/utils');
documentation = new (require('../../Documentation'));
componentDocblockHandler = require('../componentDocblockHandler');
});
it('finds docblocks for component definitions', function() {
var definition = parse([
'/**',
' * Component description',
' */',
'var Component = React.createClass({});',
].join('\n'));
componentDocblockHandler(documentation, definition);
expect(documentation.description).toBe('Component description');
});
it('ignores other types of comments', function() {
var definition = parse([
'/*',
' * This is not a docblock',
' */',
'var Component = React.createClass({});',
].join('\n'));
componentDocblockHandler(documentation, definition);
expect(documentation.description).toBe('');
definition = parse([
'// Inline comment',
'var Component = React.createClass({});',
].join('\n'));
componentDocblockHandler(documentation, definition);
expect(documentation.description).toBe('');
});
it('only considers the docblock directly above the definition', function() {
var definition = parse([
'/**',
' * This is the wrong docblock',
' */',
'var something_else = "foo";',
'var Component = React.createClass({});',
].join('\n'));
componentDocblockHandler(documentation, definition);
expect(documentation.description).toBe('');
});
});

View File

@ -1,73 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
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

@ -1,195 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
jest.mock('../../Documentation');
describe('propDocblockHandler', function() {
var utils;
var documentation;
var propDocblockHandler;
beforeEach(function() {
utils = require('../../../tests/utils');
documentation = new (require('../../Documentation'));
propDocblockHandler = require('../propDocblockHandler');
});
function parse(definition) {
var programPath = utils.parse(definition);
return programPath.get(
'body',
programPath.node.body.length - 1,
'expression'
);
}
it('finds docblocks for prop types', function() {
var definition = parse([
'({',
' propTypes: {',
' /**',
' * Foo comment',
' */',
' foo: Prop.bool,',
'',
' /**',
' * Bar comment',
' */',
' bar: Prop.bool,',
' }',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment'
},
bar: {
description: 'Bar comment'
}
});
});
it('can handle multline comments', function() {
var definition = parse([
'({',
' propTypes: {',
' /**',
' * Foo comment with',
' * many lines!',
' *',
' * even with empty lines in between',
' */',
' foo: Prop.bool',
' }',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description:
'Foo comment with\nmany lines!\n\neven with empty lines in between'
},
});
});
it('ignores non-docblock comments', function() {
var definition = parse([
'({',
' propTypes: {',
' /**',
' * Foo comment',
' */',
' // TODO: remove this comment',
' foo: Prop.bool,',
'',
' /**',
' * Bar comment',
' */',
' /* This is not a doc comment */',
' bar: Prop.bool,',
' }',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment'
},
bar: {
description: 'Bar comment'
}
});
});
it('only considers the comment with the property below it', function() {
var definition = parse([
'({',
' propTypes: {',
' /**',
' * Foo comment',
' */',
' foo: Prop.bool,',
' bar: Prop.bool,',
' }',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment'
},
bar: {
description: ''
}
});
});
it('understands and ignores the spread operator', function() {
var definition = parse([
'({',
' propTypes: {',
' ...Foo.propTypes,',
' /**',
' * Foo comment',
' */',
' foo: Prop.bool,',
' }',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment'
}
});
});
it('resolves variables', function() {
var definition = parse([
'var Props = {',
' /**',
' * Foo comment',
' */',
' foo: Prop.bool,',
'};',
'({',
' propTypes: Props',
'})'
].join('\n'));
propDocblockHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment'
}
});
});
it('does not error if propTypes cannot be found', function() {
var definition = parse([
'({',
' fooBar: 42',
'})',
].join('\n'));
expect(function() {
propDocblockHandler(documentation, definition);
}).not.toThrow();
});
});

View File

@ -1,203 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
jest.mock('../../Documentation');
describe('propTypeHandler', function() {
var utils;
var getPropTypeMock;
var documentation;
var propTypeHandler;
beforeEach(function() {
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 definition = parse([
'({',
' propTypes: {',
' foo: PropTypes.bool,',
' bar: require("react").PropTypes.bool,',
' }',
'})',
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
type: {},
required: false
},
bar: {
type: {},
required: false
}
});
});
it('finds definitions via the ReactPropTypes module', function() {
var definition = parse([
'({',
' propTypes: {',
' foo: require("ReactPropTypes").bool,',
' }',
'})',
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
foo: {
type: {},
required: false
},
});
});
it('detects whether a prop is required', function() {
var definition = parse([
'({',
' propTypes: {',
' simple_prop: PropTypes.array.isRequired,',
' complex_prop: ',
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired,',
' }',
'})'
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
simple_prop: {
type: {},
required: true
},
complex_prop: {
type: {},
required: true
}
});
});
it('only considers definitions from React or ReactPropTypes', function() {
var definition = parse([
'({',
' propTypes: {',
' custom_propA: PropTypes.bool,',
' custom_propB: Prop.bool.isRequired',
' }',
'})',
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
custom_propA: {
type: {},
required: false
},
custom_propB: {
type: {
name: 'custom',
raw: 'Prop.bool.isRequired'
},
required: false
}
});
});
it('understands the spread operator', function() {
var definition = parse([
'var Foo = require("Foo.react");',
'var props = {bar: PropTypes.bool};',
'({',
' propTypes: {',
' ...Foo.propTypes,',
' ...props,',
' foo: PropTypes.number',
' }',
'})',
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.composes).toEqual(['Foo.react']);
expect(documentation.descriptors).toEqual({
foo: {
type: {},
required: false
},
bar: {
type: {},
required: false
},
});
});
it('resolves variables', function() {
var definition = parse([
'var props = {bar: PropTypes.bool};',
'({',
' propTypes: props',
'})',
].join('\n'));
propTypeHandler(documentation, definition);
expect(documentation.descriptors).toEqual({
bar: {
type: {},
required: false
},
});
});
it('does not error if propTypes cannot be found', function() {
var definition = parse([
'({',
' fooBar: 42',
'})',
].join('\n'));
expect(function() {
propTypeHandler(documentation, definition);
}).not.toThrow();
});
});

View File

@ -1,50 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var Documentation = require('../Documentation');
var n = require('recast').types.namedTypes;
var getDocblock = require('../utils/docblock').getDocblock;
/**
* Finds the nearest block comment before the component definition.
*/
function componentDocblockHandler(
documentation: Documentation,
path: NodePath
) {
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);
}
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;

View File

@ -1,79 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var Documentation = require('../Documentation');
var 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

@ -1,16 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
exports.componentDocblockHandler = require('./componentDocblockHandler');
exports.defaultPropsHandler = require('./defaultPropsHandler');
exports.propTypeHandler = require('./propTypeHandler');
exports.propDocBlockHandler = require('./propDocBlockHandler');

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var Documentation = require('../Documentation');
var getDocblock = require('../utils/docblock').getDocblock;
var getPropertyName = require('../utils/getPropertyName');
var getPropertyValuePath = require('../utils/getPropertyValuePath');
var types = require('recast').types.namedTypes;
var resolveToValue = require('../utils/resolveToValue');
function propDocBlockHandler(documentation: Documentation, path: NodePath) {
var propTypesPath = getPropertyValuePath(path, 'propTypes');
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
return;
}
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(
getPropertyName(propertyPath)
);
propDescriptor.description = getDocblock(propertyPath) || '';
}
});
}
module.exports = propDocBlockHandler;

View File

@ -1,121 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var Documentation = require('../Documentation');
var 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;
function isPropTypesExpression(path) {
var moduleName = resolveToModule(path);
if (moduleName) {
return isReactModuleName(moduleName) || moduleName === 'ReactPropTypes';
}
return false;
}
/**
* Returns true of the prop is required, according to its type defintion
*/
function isRequired(path) {
return getMembers(path).some(
member => !member.computed && member.path.node.name === 'isRequired' ||
member.computed && member.path.node.value === 'isRequired'
);
}
/**
* Handles member expressions of the form
*
* ComponentA.propTypes
*
* it resolves ComponentA to its module name and adds it to the "composes" entry
* in the documentation.
*/
function amendComposes(documentation, path) {
var node = path.node;
if (!types.MemberExpression.check(node) ||
getNameOrValue(path.get('property')) !== 'propTypes' ||
!types.Identifier.check(node.object)) {
return;
}
var moduleName = resolveToModule(path.get('object'));
if (moduleName) {
documentation.addComposes(moduleName);
}
}
function amendPropTypes(documentation, path) {
path.get('properties').each(function(propertyPath) {
switch (propertyPath.node.type) {
case types.Property.name:
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) {
propDescriptor.type = type;
propDescriptor.required =
type.name !== 'custom' && isRequired(valuePath);
}
break;
case types.SpreadProperty.name:
var resolvedValuePath = resolveToValue(propertyPath.get('argument'));
switch (resolvedValuePath.node.type) {
case types.ObjectExpression.name: // normal object literal
amendPropTypes(documentation, resolvedValuePath);
break;
case types.MemberExpression.name:
amendComposes(documentation, resolvedValuePath);
break;
}
break;
}
});
}
function propTypeHandler(documentation: Documentation, path: NodePath) {
var propTypesPath = getPropertyValuePath(path, 'propTypes');
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath || !types.ObjectExpression.check(propTypesPath.node)) {
return;
}
switch (propTypesPath.node.type) {
case types.ObjectExpression.name:
amendPropTypes(documentation, propTypesPath);
break;
case types.MemberExpression.name:
amendComposes(documentation, propTypesPath);
}
}
module.exports = propTypeHandler;

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"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,
];
/**
* 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).
*/
function defaultParse(
src: string,
resolver?: ?Resolver,
handlers?: ?Array<Handler>
): (Array<Object>|Object) {
if (!resolver) {
resolver = defaultResolver;
}
if (!handlers) {
handlers = defaultHandlers;
}
return parse(src, resolver, handlers);
}
module.exports = {
parse: defaultParse,
handlers,
resolver,
utils
};

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var Documentation = require('./Documentation');
var 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

@ -1,106 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('React documentation parser', function() {
var findAllReactCreateClassCalls;
var recast;
function parse(source) {
return findAllReactCreateClassCalls(
recast.parse(source).program,
recast
);
}
beforeEach(function() {
findAllReactCreateClassCalls = require('../findAllReactCreateClassCalls');
recast = require('recast');
});
it('finds React.createClass', function() {
var source = [
'var React = require("React");',
'var Component = React.createClass({});',
'module.exports = Component;'
].join('\n');
var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
expect(result[0] instanceof recast.types.NodePath).toBe(true);
expect(result[0].node.type).toBe('ObjectExpression');
});
it('finds React.createClass, independent of the var name', function() {
var source = [
'var R = require("React");',
'var Component = R.createClass({});',
'module.exports = Component;'
].join('\n');
var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
});
it('does not process X.createClass of other modules', function() {
var source = [
'var R = require("NoReact");',
'var Component = R.createClass({});',
'module.exports = Component;'
].join('\n');
var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(0);
});
it('finds assignments to exports', function() {
var source = [
'var R = require("React");',
'var Component = R.createClass({});',
'exports.foo = 42;',
'exports.Component = Component;'
].join('\n');
var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
});
it('accepts multiple definitions', function() {
var source = [
'var R = require("React");',
'var ComponentA = R.createClass({});',
'var ComponentB = R.createClass({});',
'exports.ComponentB = ComponentB;'
].join('\n');
var result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(2);
source = [
'var R = require("React");',
'var ComponentA = R.createClass({});',
'var ComponentB = R.createClass({});',
'module.exports = ComponentB;'
].join('\n');
result = parse(source);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(2);
});
});

View File

@ -1,106 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('React documentation parser', function() {
var findExportedReactCreateClass;
var recast;
function parse(source) {
return findExportedReactCreateClass(
recast.parse(source).program,
recast
);
}
beforeEach(function() {
findExportedReactCreateClass =
require('../findExportedReactCreateClassCall');
recast = require('recast');
});
it('finds React.createClass', function() {
var source = [
'var React = require("React");',
'var Component = React.createClass({});',
'module.exports = Component;'
].join('\n');
expect(parse(source)).toBeDefined();
});
it('finds React.createClass, independent of the var name', function() {
var source = [
'var R = require("React");',
'var Component = R.createClass({});',
'module.exports = Component;'
].join('\n');
expect(parse(source)).toBeDefined();
});
it('does not process X.createClass of other modules', function() {
var source = [
'var R = require("NoReact");',
'var Component = R.createClass({});',
'module.exports = Component;'
].join('\n');
expect(parse(source)).toBeUndefined();
});
it('finds assignments to exports', function() {
var source = [
'var R = require("React");',
'var Component = R.createClass({});',
'exports.foo = 42;',
'exports.Component = Component;'
].join('\n');
expect(parse(source)).toBeDefined();
});
it('errors if multiple components are exported', function() {
var source = [
'var R = require("React");',
'var ComponentA = R.createClass({});',
'var ComponentB = R.createClass({});',
'exports.ComponentA = ComponentA;',
'exports.ComponentB = ComponentB;'
].join('\n');
expect(function() {
parse(source)
}).toThrow();
});
it('accepts multiple definitions if only one is exported', function() {
var source = [
'var R = require("React");',
'var ComponentA = R.createClass({});',
'var ComponentB = R.createClass({});',
'exports.ComponentB = ComponentB;'
].join('\n');
expect(parse(source)).toBeDefined();
source = [
'var R = require("React");',
'var ComponentA = R.createClass({});',
'var ComponentB = R.createClass({});',
'module.exports = ComponentB;'
].join('\n');
expect(parse(source)).toBeDefined();
});
});

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var isReactCreateClassCall = require('../utils/isReactCreateClassCall');
var resolveToValue = require('../utils/resolveToValue');
/**
* Given an AST, this function tries to find all object expressions that are
* passed to `React.createClass` calls, by resolving all references properly.
*/
function findAllReactCreateClassCalls(
ast: ASTNode,
recast: Object
): Array<NodePath> {
var types = recast.types.namedTypes;
var definitions = [];
recast.visit(ast, {
visitCallExpression: function(path) {
if (!isReactCreateClassCall(path)) {
return false;
}
// We found React.createClass. Lets get cracking!
var resolvedPath = resolveToValue(path.get('arguments', 0));
if (types.ObjectExpression.check(resolvedPath.node)) {
definitions.push(resolvedPath);
}
return false;
}
});
return definitions;
}
module.exports = findAllReactCreateClassCalls;

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var isExportsOrModuleAssignment =
require('../utils/isExportsOrModuleAssignment');
var isReactCreateClassCall = require('../utils/isReactCreateClassCall');
var resolveToValue = require('../utils/resolveToValue');
var ERROR_MULTIPLE_DEFINITIONS =
'Multiple exported component definitions found.';
function ignore() {
return false;
}
/**
* Given an AST, this function tries to find the object expression that is
* passed to `React.createClass`, by resolving all references properly.
*/
function findExportedReactCreateClass(
ast: ASTNode,
recast: Object
): ?NodePath {
var types = recast.types.namedTypes;
var definition;
recast.visit(ast, {
visitFunctionDeclaration: ignore,
visitFunctionExpression: ignore,
visitIfStatement: ignore,
visitWithStatement: ignore,
visitSwitchStatement: ignore,
visitCatchCause: ignore,
visitWhileStatement: ignore,
visitDoWhileStatement: ignore,
visitForStatement: ignore,
visitForInStatement: ignore,
visitAssignmentExpression: function(path) {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
if (!isExportsOrModuleAssignment(path)) {
return false;
}
// Resolve the value of the right hand side. It should resolve to a call
// expression, something like React.createClass
path = resolveToValue(path.get('right'));
if (!isReactCreateClassCall(path)) {
return false;
}
if (definition) {
// If a file exports multiple components, ... complain!
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
}
// We found React.createClass. Lets get cracking!
var resolvedPath = resolveToValue(path.get('arguments', 0));
if (types.ObjectExpression.check(resolvedPath.node)) {
definition = resolvedPath;
}
return false;
}
});
return definition;
}
module.exports = findExportedReactCreateClass;

View File

@ -1,16 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
exports.findAllReactCreateClassCalls =
require('./findAllReactCreateClassCalls');
exports.findExportedReactCreateClassCall =
require('./findExportedReactCreateClassCall');

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('docblock', function() {
describe('getDoclets', function() {
var getDoclets;
beforeEach(function() {
getDoclets = require('../docblock').getDoclets;
});
it('extacts single line doclets', function() {
expect(getDoclets('@foo bar\n@bar baz'))
.toEqual({foo: 'bar', bar: 'baz'});
});
it('extacts multi line doclets', function() {
expect(getDoclets('@foo bar\nbaz\n@bar baz'))
.toEqual({foo: 'bar\nbaz', bar: 'baz'});
});
it('extacts boolean doclets', function() {
expect(getDoclets('@foo bar\nbaz\n@abc\n@bar baz'))
.toEqual({foo: 'bar\nbaz', abc: true, bar: 'baz'});
});
});
});

View File

@ -1,51 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('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

@ -1,144 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('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

@ -1,42 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('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,46 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('isExportsOrModuleAssignment', function() {
var recast;
var isExportsOrModuleAssignment;
function parse(src) {
return new recast.types.NodePath(
recast.parse(src).program.body[0]
);
}
beforeEach(function() {
isExportsOrModuleAssignment = require('../isExportsOrModuleAssignment');
recast = require('recast');
});
it('detects "module.exports = ...;"', function() {
expect(isExportsOrModuleAssignment(parse('module.exports = foo;')))
.toBe(true);
});
it('detects "exports.foo = ..."', function() {
expect(isExportsOrModuleAssignment(parse('exports.foo = foo;')))
.toBe(true);
});
it('does not accept "exports = foo;"', function() {
// That doesn't actually export anything
expect(isExportsOrModuleAssignment(parse('exports = foo;')))
.toBe(false);
});
});

View File

@ -1,41 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
jest.autoMockOff();
describe('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

@ -1,60 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* Helper functions to work with docblock comments.
* @flow
*/
"use strict";
var types = require('recast').types.namedTypes;
var docletPattern = /^@(\w+)(?:$|\s((?:[^](?!^@\w))*))/gmi;
function parseDocblock(str) {
var lines = str.split('\n');
for (var i = 0, l = lines.length; i < l; i++) {
lines[i] = lines[i].replace(/^\s*\*\s?/, '');
}
return lines.join('\n').trim();
}
/**
* Given a path, this function returns the closest preceding docblock if it
* exists.
*/
function getDocblock(path: NodePath): ?string {
if (path.node.comments) {
var comments = path.node.comments.leading.filter(function(comment) {
return comment.type === 'Block' && comment.value.indexOf('*\n') === 0;
});
if (comments.length > 0) {
return parseDocblock(comments[comments.length - 1].value);
}
}
return null;
}
/**
* Given a string, this functions returns an object with doclet names as keys
* and their "content" as values.
*/
function getDoclets(str: string): Object {
var doclets = Object.create(null);
var match = docletPattern.exec(str);
for (; match; match = docletPattern.exec(str)) {
doclets[match[1]] = match[2] || true;
}
return doclets;
}
exports.getDocblock = getDocblock;
exports.getDoclets = getDoclets;

View File

@ -1,80 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var resolveToValue = require('./resolveToValue');
var types = require('recast').types.namedTypes;
/**
* Splits a MemberExpression or CallExpression into parts.
* E.g. foo.bar.baz becomes ['foo', 'bar', 'baz']
*/
function toArray(path: NodePath): Array<string> {
var parts = [path];
var result = [];
while (parts.length > 0) {
path = parts.shift();
var node = path.node;
if (types.CallExpression.check(node)) {
parts.push(path.get('callee'));
continue;
} else if (types.MemberExpression.check(node)) {
parts.push(path.get('object'));
if (node.computed) {
var resolvedPath = resolveToValue(path.get('property'));
if (resolvedPath !== undefined) {
result = result.concat(toArray(resolvedPath));
} else {
result.push('<computed>');
}
} else {
result.push(node.property.name);
}
continue;
} else if (types.Identifier.check(node)) {
result.push(node.name);
continue;
} else if (types.Literal.check(node)) {
result.push(node.raw);
continue;
} else if (types.ThisExpression.check(node)) {
result.push('this');
continue;
} else if (types.ObjectExpression.check(node)) {
var properties = path.get('properties').map(function(property) {
return toString(property.get('key')) +
': ' +
toString(property.get('value'));
});
result.push('{' + properties.join(', ') + '}');
continue;
} else if(types.ArrayExpression.check(node)) {
result.push('[' + path.get('elements').map(toString).join(', ') + ']');
continue;
}
}
return result.reverse();
}
/**
* Creates a string representation of a member expression.
*/
function toString(path: NodePath): string {
return toArray(path).join('.');
}
exports.String = toString;
exports.Array = toArray;

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* 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

@ -1,34 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var types = require('recast').types.namedTypes;
/**
* If node is an Identifier, it returns its name. If it is a literal, it returns
* its value.
*/
function getNameOrValue(path: NodePath, raw?: boolean): string {
var node = path.node;
switch (node.type) {
case types.Identifier.name:
return node.name;
case types.Literal.name:
return raw ? node.raw : node.value;
default:
throw new TypeError('Argument must be an Identifier or a Literal');
}
}
module.exports = getNameOrValue;

View File

@ -1,153 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var 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

@ -1,32 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var getNameOrValue = require('./getNameOrValue');
var types = require('recast').types.namedTypes;
/**
* In an ObjectExpression, the name of a property can either be an identifier
* or a literal (or dynamic, but we don't support those). This function simply
* returns the value of the literal or name of the identifier.
*/
function getPropertyName(propertyPath: NodePath): string {
if (propertyPath.node.computed) {
throw new TypeError('Propery name must be an Identifier or a Literal');
}
return getNameOrValue(propertyPath.get('key'), false);
}
module.exports = getPropertyName;

View File

@ -1,31 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var 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;

View File

@ -1,23 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
"use strict";
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

@ -1,37 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var expressionTo = require('./expressionTo');
var types = require('recast').types.namedTypes;
/**
* Returns true if the expression is of form `exports.foo = ...;` or
* `modules.exports = ...;`.
*/
function isExportsOrModuleAssignment(path: NodePath): boolean {
if (types.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
if (!types.AssignmentExpression.check(path.node) ||
!types.MemberExpression.check(path.node.left)) {
return false;
}
var exprArr = expressionTo.Array(path.get('left'));
return (exprArr[0] === 'module' && exprArr[1] === 'exports') ||
exprArr[0] == 'exports';
}
module.exports = isExportsOrModuleAssignment;

View File

@ -1,37 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var isReactModuleName = require('./isReactModuleName');
var match = require('./match');
var resolveToModule = require('./resolveToModule');
var types = require('recast').types.namedTypes;
/**
* Returns true if the expression is a function call of the form
* `React.createClass(...)`.
*/
function isReactCreateClassCall(path: NodePath): boolean {
if (types.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
if (!match(path.node, {callee: {property: {name: 'createClass'}}})) {
return false;
}
var module = resolveToModule(path.get('callee', 'object'));
return module && isReactModuleName(module);
}
module.exports = isReactCreateClassCall;

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var reactModules = ['react', 'react/addons'];
/**
* Takes a module name (string) and returns true if it refers to a root react
* module name.
*/
function isReactModuleName(moduleName: string): boolean {
return reactModules.some(function(reactModuleName) {
return reactModuleName === moduleName.toLowerCase();
});
}
module.exports = isReactModuleName;

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
/**
* This function takes an AST node and matches it against "pattern". Pattern
* is simply a (nested) object literal and it is traversed to see whether node
* contains those (nested) properties with the provided values.
*/
function match(node: ASTNOde, pattern: Object): boolean {
if (!node) {
return false;
}
for (var prop in pattern) {
if (!node[prop]) {
return false;
}
if (pattern[prop] && typeof pattern[prop] === 'object') {
if (!match(node[prop], pattern[prop])) {
return false;
}
} else if (node[prop] !== pattern[prop]) {
return false;
}
}
return true;
}
module.exports = match;

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var match = require('./match');
var resolveToValue = require('./resolveToValue');
var types = require('recast').types.namedTypes;
/**
* Given a path (e.g. call expression, member expression or identifier),
* this function tries to find the name of module from which the "root value"
* was imported.
*/
function resolveToModule(path: NodePath): ?string {
var node = path.node;
switch (node.type) {
case types.VariableDeclarator.name:
if (node.init) {
return resolveToModule(path.get('init'));
}
break;
case types.CallExpression.name:
if (match(node.callee, {type: types.Identifier.name, name: 'require'})) {
return node['arguments'][0].value;
}
return resolveToModule(path.get('callee'));
case types.Identifier.name:
var valuePath = resolveToValue(path);
if (valuePath !== path) {
return resolveToModule(valuePath);
}
break;
case types.MemberExpression.name:
while (path && types.MemberExpression.check(path.node)) {
path = path.get('object');
}
if (path) {
return resolveToModule(path);
}
}
}
module.exports = resolveToModule;

View File

@ -1,46 +0,0 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
/**
* @flow
*/
"use strict";
var types = require('recast').types.namedTypes;
/**
* If the path is an identifier, it is resolved in the scope chain.
* If it is an assignment expression, it resolves to the right hand side.
*
* Else the path itself is returned.
*/
function resolveToValue(path: NodePath): NodePath {
var node = path.node;
if (types.AssignmentExpression.check(node)) {
if (node.operator === '=') {
return resolveToValue(node.get('right'));
}
} else if (types.Identifier.check(node)) {
var scope = path.scope.lookup(node.name);
if (scope) {
var bindings = scope.getBindings()[node.name];
if (bindings.length > 0) {
var parentPath = scope.getBindings()[node.name][0].parent;
if (types.VariableDeclarator.check(parentPath.node)) {
parentPath = parentPath.get('init');
}
return resolveToValue(parentPath);
}
}
}
return path;
}
module.exports = resolveToValue;

View File

@ -1,41 +0,0 @@
{
"name": "react-docgen",
"version": "1.0.0",
"description": "A CLI and toolkit to extract information from React components for documentation generation.",
"repository": {
"type": "git",
"url": "https://github.com/reactjs/react-docgen.git"
},
"bugs": "https://github.com/reactjs/react-docgen/issues",
"bin": {
"react-docgen": "bin/react-docgen.js"
},
"main": "dist/main.js",
"scripts": {
"watch": "jsx lib/ dist/ --harmony --strip-types -w",
"build": "rm -rf dist/ && jsx lib/ dist/ --harmony --strip-types --no-cache-dir",
"prepublish": "npm run build",
"test": "jest"
},
"keywords": [
"react",
"documentation-generation"
],
"author": "Felix Kling",
"license": "BSD-3-Clause",
"dependencies": {
"async": "^0.9.0",
"node-dir": "^0.1.6",
"nomnom": "^1.8.1",
"recast": "^0.9.17"
},
"devDependencies": {
"jest-cli": "^0.3.0",
"react-tools": "^0.12.2"
},
"jest": {
"scriptPreprocessor": "./tests/preprocessor",
"testPathDirs": ["lib"],
"unmockedModulePathPatterns": ["tests/utils"]
}
}

View File

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

View File

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

View File

@ -1,49 +0,0 @@
"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,4 +1,4 @@
var docs = require('../react-docgen');
var docs = require('react-docgen');
var fs = require('fs');
var path = require('path');
var slugify = require('../core/slugify');