Adding react-docgen for documentation generation
Usage: cd website/react-docgen && npm install or: cd website/react-docgen && npm install -g ./
This commit is contained in:
parent
bd53b4191f
commit
ba7021f6f8
|
@ -0,0 +1,30 @@
|
||||||
|
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.
|
|
@ -0,0 +1,23 @@
|
||||||
|
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 Facebook’s 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.
|
|
@ -0,0 +1,175 @@
|
||||||
|
# react-docs-generator
|
||||||
|
|
||||||
|
`react-docs-generator` extracts information from React components with which
|
||||||
|
you can generate documentation for those components.
|
||||||
|
|
||||||
|
It uses esprima-fb 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Install the module directly from npm:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install -g react-docs-generator
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
Installing the module adds a `react-docs` 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-docs [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.
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
The tool can also be used programmatically to extract component information:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var reactDocs = require('react-docs-generator');
|
||||||
|
var componentInfo reactDocs.parseSource(src);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- Modules have to export a single component, and only that component is
|
||||||
|
analyzed.
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
## 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>]
|
||||||
|
},
|
||||||
|
"required": boolean,
|
||||||
|
"description": string,
|
||||||
|
["defaultValue": {
|
||||||
|
"value": number | string,
|
||||||
|
"computed": boolean
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
["composes": <componentNames>]
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,168 @@
|
||||||
|
#!/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-docs')
|
||||||
|
.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.parseSource(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.parseSource(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.parseSource(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);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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>;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
type PropDescriptor = {
|
||||||
|
type?: {
|
||||||
|
name: string;
|
||||||
|
value?: any;
|
||||||
|
};
|
||||||
|
required?: boolean;
|
||||||
|
defaultValue?: any;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How this parser works:
|
||||||
|
*
|
||||||
|
* 1. For each given file path do:
|
||||||
|
*
|
||||||
|
* a. Find component definition
|
||||||
|
* -. Find the rvalue module.exports assignment.
|
||||||
|
* Otherwise inspect assignments to exports. If there are multiple
|
||||||
|
* components that are exported, we don't continue with parsing the file.
|
||||||
|
* -. If the previous step results in a variable name, resolve it.
|
||||||
|
* -. Extract the object literal from the React.createClass call.
|
||||||
|
*
|
||||||
|
* b. Execute definition handlers (handlers working with the object
|
||||||
|
* expression).
|
||||||
|
*
|
||||||
|
* c. For each property of the definition object, execute the registered
|
||||||
|
* callbacks, if they are eligible for this property.
|
||||||
|
*
|
||||||
|
* 2. Return the aggregated results
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Handler = (documentation: Documentation, path: NodePath) => void;
|
||||||
|
|
||||||
|
var Documentation = require('./Documentation');
|
||||||
|
|
||||||
|
var expressionTo = require('./utils/expressionTo');
|
||||||
|
var getPropertyName = require('./utils/getPropertyName');
|
||||||
|
var isReactModuleName = require('./utils/isReactModuleName');
|
||||||
|
var match = require('./utils/match');
|
||||||
|
var resolveToValue = require('./utils/resolveToValue');
|
||||||
|
var resolveToModule = require('./utils/resolveToModule');
|
||||||
|
var recast = require('recast');
|
||||||
|
var n = recast.types.namedTypes;
|
||||||
|
|
||||||
|
function ignore() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the statement is of form `foo = bar;`.
|
||||||
|
*
|
||||||
|
* @param {object} node
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
function isAssignmentStatement(node) {
|
||||||
|
return match(node, {expression: {operator: '='}});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the expression is of form `exports.foo = bar;` or
|
||||||
|
* `modules.exports = foo;`.
|
||||||
|
*
|
||||||
|
* @param {object} node
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
function isExportsOrModuleExpression(path) {
|
||||||
|
if (!n.AssignmentExpression.check(path.node) ||
|
||||||
|
!n.MemberExpression.check(path.node.left)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var exprArr = expressionTo.Array(path.get('left'));
|
||||||
|
return (exprArr[0] === 'module' && exprArr[1] === 'exports') ||
|
||||||
|
exprArr[0] == 'exports';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the expression is a function call of the form
|
||||||
|
* `React.createClass(...)`.
|
||||||
|
*
|
||||||
|
* @param {object} node
|
||||||
|
* @param {array} scopeChain
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
function isReactCreateClassCall(path) {
|
||||||
|
if (!match(path.node, {callee: {property: {name: 'createClass'}}})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var module = resolveToModule(path.get('callee', 'object'));
|
||||||
|
return module && isReactModuleName(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an AST, this function tries to find the object expression that is
|
||||||
|
* passed to `React.createClass`, by resolving all references properly.
|
||||||
|
*
|
||||||
|
* @param {object} ast
|
||||||
|
* @return {?object}
|
||||||
|
*/
|
||||||
|
function findComponentDefinition(ast) {
|
||||||
|
var definition;
|
||||||
|
|
||||||
|
recast.visit(ast, {
|
||||||
|
visitFunctionDeclaration: ignore,
|
||||||
|
visitFunctionExpression: ignore,
|
||||||
|
visitIfStatement: ignore,
|
||||||
|
visitWithStatement: ignore,
|
||||||
|
visitSwitchStatement: ignore,
|
||||||
|
visitTryStatement: ignore,
|
||||||
|
visitWhileStatement: ignore,
|
||||||
|
visitDoWhileStatement: ignore,
|
||||||
|
visitForStatement: ignore,
|
||||||
|
visitForInStatement: ignore,
|
||||||
|
visitAssignmentExpression: function(path) {
|
||||||
|
// Ignore anything that is not `exports.X = ...;` or
|
||||||
|
// `module.exports = ...;`
|
||||||
|
if (!isExportsOrModuleExpression(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(ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS);
|
||||||
|
}
|
||||||
|
// We found React.createClass. Lets get cracking!
|
||||||
|
definition = resolveToValue(path.get('arguments', 0));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ReactDocumentationParser {
|
||||||
|
_componentHandlers: Array<Handler>;
|
||||||
|
_propertyHandlers: Object<string, Handler>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._componentHandlers = [];
|
||||||
|
this._propertyHandlers = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handlers extract information from the component definition.
|
||||||
|
*
|
||||||
|
* If "property" is not provided, the handler is passed the whole component
|
||||||
|
* definition.
|
||||||
|
*/
|
||||||
|
addHandler(handler: Handler, property?: string): void {
|
||||||
|
if (!property) {
|
||||||
|
this._componentHandlers.push(handler);
|
||||||
|
} else {
|
||||||
|
if (!this._propertyHandlers[property]) {
|
||||||
|
this._propertyHandlers[property] = [];
|
||||||
|
}
|
||||||
|
this._propertyHandlers[property].push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes JavaScript source code and returns an object with the information
|
||||||
|
* extract from it.
|
||||||
|
*/
|
||||||
|
parseSource(source: string): Object {
|
||||||
|
var documentation = new Documentation();
|
||||||
|
var ast = recast.parse(source);
|
||||||
|
// Find the component definition first. The return value should be
|
||||||
|
// an ObjectExpression.
|
||||||
|
var componentDefinition = findComponentDefinition(ast.program);
|
||||||
|
if (!componentDefinition) {
|
||||||
|
throw new Error(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute all the handlers to extract the information
|
||||||
|
this._executeHandlers(documentation, componentDefinition);
|
||||||
|
|
||||||
|
return documentation.toObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
_executeHandlers(documentation, componentDefinition: NodePath) {
|
||||||
|
componentDefinition.get('properties').each(propertyPath => {
|
||||||
|
var name = getPropertyName(propertyPath);
|
||||||
|
if (!this._propertyHandlers[name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var propertyValuePath = propertyPath.get('value');
|
||||||
|
this._propertyHandlers[name].forEach(
|
||||||
|
handler => handler(documentation, propertyValuePath)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._componentHandlers.forEach(
|
||||||
|
handler => handler(documentation, componentDefinition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDocumentationParser.ERROR_MISSING_DEFINITION =
|
||||||
|
'No suitable component definition found.';
|
||||||
|
|
||||||
|
ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS =
|
||||||
|
'Multiple exported component definitions found.';
|
||||||
|
|
||||||
|
module.exports = ReactDocumentationParser;
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
require('mock-modules').autoMockOff();
|
||||||
|
|
||||||
|
describe('React documentation parser', function() {
|
||||||
|
var ReactDocumentationParser;
|
||||||
|
var parser;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
ReactDocumentationParser = require('../ReactDocumentationParser');
|
||||||
|
parser = new ReactDocumentationParser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors if component definition is not found', function() {
|
||||||
|
var source = 'var React = require("React");';
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds React.createClass', function() {
|
||||||
|
var source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'var Component = React.createClass({});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
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(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
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(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION);
|
||||||
|
});
|
||||||
|
|
||||||
|
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(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).toThrow(ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS);
|
||||||
|
});
|
||||||
|
|
||||||
|
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(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).not.toThrow();
|
||||||
|
|
||||||
|
source = [
|
||||||
|
'var R = require("React");',
|
||||||
|
'var ComponentA = R.createClass({});',
|
||||||
|
'var ComponentB = R.createClass({});',
|
||||||
|
'module.exports = ComponentB;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
parser.parseSource(source);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
96
website/react-docgen/lib/handlers/__tests__/componentDocblockHandler-test.js
vendored
Normal file
96
website/react-docgen/lib/handlers/__tests__/componentDocblockHandler-test.js
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* 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 parser;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
parser = new (require('../../ReactDocumentationParser'));
|
||||||
|
parser.addHandler(require('../componentDocblockHandler'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds docblocks for component definitions', function() {
|
||||||
|
var source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'/**',
|
||||||
|
' * Component description',
|
||||||
|
' */',
|
||||||
|
'var Component = React.createClass({});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
props: {},
|
||||||
|
description: 'Component description'
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores other types of comments', function() {
|
||||||
|
var source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'/*',
|
||||||
|
' * This is not a docblock',
|
||||||
|
' */',
|
||||||
|
'var Component = React.createClass({});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
props: {},
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
|
||||||
|
|
||||||
|
source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'// Inline comment',
|
||||||
|
'var Component = React.createClass({});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expectedResult = {
|
||||||
|
props: {},
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only considers the docblock directly above the definition', function() {
|
||||||
|
var source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'/**',
|
||||||
|
' * This is the wrong docblock',
|
||||||
|
' */',
|
||||||
|
'var something_else = "foo";',
|
||||||
|
'var Component = React.createClass({});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
props: {},
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
jest.autoMockOff();
|
||||||
|
|
||||||
|
var module_template = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'var PropTypes = React.PropTypes;',
|
||||||
|
'var Component = React.createClass(%s);',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
function getSource(definition) {
|
||||||
|
return module_template.replace('%s', definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('React documentation parser', function() {
|
||||||
|
var parser;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
parser = new (require('../../ReactDocumentationParser'));
|
||||||
|
parser.addHandler(require('../defaultValueHandler'), 'getDefaultProps');
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('should find prop default values that are literals', function() {
|
||||||
|
var source = getSource([
|
||||||
|
'{',
|
||||||
|
' getDefaultProps: function() {',
|
||||||
|
' return {',
|
||||||
|
' foo: "bar",',
|
||||||
|
' bar: 42,',
|
||||||
|
' baz: ["foo", "bar"],',
|
||||||
|
' abc: {xyz: abc.def, 123: 42}',
|
||||||
|
' };',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
defaultValue: {
|
||||||
|
value: '"bar"',
|
||||||
|
computed: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
defaultValue: {
|
||||||
|
value: '42',
|
||||||
|
computed: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
defaultValue: {
|
||||||
|
value: '["foo", "bar"]',
|
||||||
|
computed: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
abc: {
|
||||||
|
defaultValue: {
|
||||||
|
value: '{xyz: abc.def, 123: 42}',
|
||||||
|
computed: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
jest.autoMockOff();
|
||||||
|
|
||||||
|
var module_template = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'var PropTypes = React.PropTypes;',
|
||||||
|
'var Component = React.createClass(%s);',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
function getSource(definition) {
|
||||||
|
return module_template.replace('%s', definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('React documentation parser', function() {
|
||||||
|
var parser;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
parser = new (require('../../ReactDocumentationParser'));
|
||||||
|
parser.addHandler(require('../propDocblockHandler'), 'propTypes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds docblocks for prop types', function() {
|
||||||
|
var source = getSource([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' /**',
|
||||||
|
' * Foo comment',
|
||||||
|
' */',
|
||||||
|
' foo: Prop.bool,',
|
||||||
|
'',
|
||||||
|
' /**',
|
||||||
|
' * Bar comment',
|
||||||
|
' */',
|
||||||
|
' bar: Prop.bool,',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
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([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' /**',
|
||||||
|
' * Foo comment with',
|
||||||
|
' * many lines!',
|
||||||
|
' *',
|
||||||
|
' * even with empty lines in between',
|
||||||
|
' */',
|
||||||
|
' foo: Prop.bool',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
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([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' /**',
|
||||||
|
' * Foo comment',
|
||||||
|
' */',
|
||||||
|
' // TODO: remove this comment',
|
||||||
|
' foo: Prop.bool,',
|
||||||
|
'',
|
||||||
|
' /**',
|
||||||
|
' * Bar comment',
|
||||||
|
' */',
|
||||||
|
' /* This is not a doc comment */',
|
||||||
|
' bar: Prop.bool,',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
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([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' /**',
|
||||||
|
' * Foo comment',
|
||||||
|
' */',
|
||||||
|
' foo: Prop.bool,',
|
||||||
|
' bar: Prop.bool,',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
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([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' ...Foo.propTypes,',
|
||||||
|
' /**',
|
||||||
|
' * Foo comment',
|
||||||
|
' */',
|
||||||
|
' foo: Prop.bool,',
|
||||||
|
' bar: Prop.bool,',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
description: 'Foo comment'
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,456 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
jest.autoMockOff();
|
||||||
|
|
||||||
|
var module_template = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'var PropTypes = React.PropTypes;',
|
||||||
|
'var Component = React.createClass(%s);',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
function getSource(definition) {
|
||||||
|
return module_template.replace('%s', definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('React documentation parser', function() {
|
||||||
|
var parser;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
parser = new (require('../../ReactDocumentationParser'));
|
||||||
|
parser.addHandler(require('../propTypeHandler'), 'propTypes');
|
||||||
|
});
|
||||||
|
|
||||||
|
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({',
|
||||||
|
' propTypes: {',
|
||||||
|
' foo: Prop.bool,',
|
||||||
|
' bar: Prop1.bool,',
|
||||||
|
' }',
|
||||||
|
'});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
type: {name: 'bool'},
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
type: {name: 'bool'},
|
||||||
|
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({',
|
||||||
|
' propTypes: {',
|
||||||
|
' foo: Prop.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.only('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'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
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'},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves variables to their values', function() {
|
||||||
|
var source = [
|
||||||
|
'var React = require("React");',
|
||||||
|
'var PropTypes = React.PropTypes;',
|
||||||
|
'var shape = {bar: PropTypes.string};',
|
||||||
|
'var Component = React.createClass({',
|
||||||
|
' propTypes: {',
|
||||||
|
' foo: PropTypes.shape(shape)',
|
||||||
|
' }',
|
||||||
|
'});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
type: {
|
||||||
|
name: 'shape',
|
||||||
|
value: {
|
||||||
|
bar: {name: 'string'}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects whether a prop is required', function() {
|
||||||
|
var source = getSource([
|
||||||
|
'{',
|
||||||
|
' 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: ',
|
||||||
|
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired,',
|
||||||
|
' instanceOf_prop: PropTypes.instanceOf(Foo).isRequired',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props:{
|
||||||
|
array_prop: {
|
||||||
|
type: {name: 'array'},
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = parser.parseSource(source);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects custom validation functions', function() {
|
||||||
|
var source = getSource([
|
||||||
|
'{',
|
||||||
|
' propTypes: {',
|
||||||
|
' custom_prop: function() {}',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
custom_prop: {
|
||||||
|
type: {name: 'custom'},
|
||||||
|
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({',
|
||||||
|
' propTypes: {',
|
||||||
|
' custom_propA: PropTypes.bool,',
|
||||||
|
' custom_propB: Prop.bool.isRequired',
|
||||||
|
' }',
|
||||||
|
'});',
|
||||||
|
'module.exports = Component;'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var expectedResult = {
|
||||||
|
description: '',
|
||||||
|
props: {
|
||||||
|
custom_propA: {
|
||||||
|
type: {name: 'bool'},
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
custom_propB: {
|
||||||
|
type: {name: 'custom'},
|
||||||
|
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 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');
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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 = '';
|
||||||
|
// 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) || '';
|
||||||
|
}
|
||||||
|
documentation.setDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = componentDocblockHandler;
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Documentation = require('../Documentation');
|
||||||
|
|
||||||
|
var expressionTo = require('../utils/expressionTo');
|
||||||
|
var getPropertyName = require('../utils/getPropertyName');
|
||||||
|
var recast = require('recast');
|
||||||
|
var resolveToValue = require('../utils/resolveToValue');
|
||||||
|
var types = recast.types.namedTypes;
|
||||||
|
var visit = recast.types.visit;
|
||||||
|
|
||||||
|
function getDefaultValue(path) {
|
||||||
|
var node = path.node;
|
||||||
|
var defaultValue;
|
||||||
|
if (types.Literal.check(node)) {
|
||||||
|
defaultValue = node.raw;
|
||||||
|
} else {
|
||||||
|
path = resolveToValue(path);
|
||||||
|
node = path.node;
|
||||||
|
defaultValue = expressionTo.String(path);
|
||||||
|
}
|
||||||
|
if (typeof defaultValue !== 'undefined') {
|
||||||
|
return {
|
||||||
|
value: defaultValue,
|
||||||
|
computed: types.CallExpression.check(node) ||
|
||||||
|
types.MemberExpression.check(node) ||
|
||||||
|
types.Identifier.check(node)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultValueHandler(documentation: Documentation, path: NodePath) {
|
||||||
|
if (!types.FunctionExpression.check(path.node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the value that is returned from the function and process it if it is
|
||||||
|
// an object literal.
|
||||||
|
var objectExpressionPath;
|
||||||
|
visit(path.get('body'), {
|
||||||
|
visitFunction: () => false,
|
||||||
|
visitReturnStatement: function(path) {
|
||||||
|
var resolvedPath = resolveToValue(path.get('argument'));
|
||||||
|
if (types.ObjectExpression.check(resolvedPath.node)) {
|
||||||
|
objectExpressionPath = resolvedPath;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (objectExpressionPath) {
|
||||||
|
objectExpressionPath.get('properties').each(function(propertyPath) {
|
||||||
|
var propDescriptor = documentation.getPropDescriptor(
|
||||||
|
getPropertyName(propertyPath)
|
||||||
|
);
|
||||||
|
var defaultValue = getDefaultValue(propertyPath.get('value'));
|
||||||
|
if (defaultValue) {
|
||||||
|
propDescriptor.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = defaultValueHandler;
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 types = require('recast').types.namedTypes;
|
||||||
|
var getDocblock = require('../utils/docblock').getDocblock;
|
||||||
|
var getPropertyName = require('../utils/getPropertyName');
|
||||||
|
|
||||||
|
function propDocBlockHandler(documentation: Documentation, path: NodePath) {
|
||||||
|
if (!types.ObjectExpression.check(path.node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.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;
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var Documentation = require('../Documentation');
|
||||||
|
|
||||||
|
var expressionTo = require('../utils/expressionTo');
|
||||||
|
var getNameOrValue = require('../utils/getNameOrValue');
|
||||||
|
var getPropertyName = require('../utils/getPropertyName');
|
||||||
|
var isReactModuleName = require('../utils/isReactModuleName');
|
||||||
|
var resolveToModule = require('../utils/resolveToModule');
|
||||||
|
var resolveToValue = require('../utils/resolveToValue');
|
||||||
|
var types = require('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) {
|
||||||
|
return isReactModuleName(moduleName) || moduleName === 'ReactPropTypes';
|
||||||
|
}
|
||||||
|
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.FunctionExpression.check(node) || !isPropTypesExpression(path)) {
|
||||||
|
return {name: 'custom'};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 type = getPropType(propertyPath.get('value'));
|
||||||
|
if (type) {
|
||||||
|
var propDescriptor = documentation.getPropDescriptor(
|
||||||
|
getPropertyName(propertyPath)
|
||||||
|
);
|
||||||
|
propDescriptor.type = type;
|
||||||
|
propDescriptor.required = type.name !== 'custom' &&
|
||||||
|
isRequired(propertyPath.get('value'));
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
path = resolveToValue(path);
|
||||||
|
switch (path.node.type) {
|
||||||
|
case types.ObjectExpression.name:
|
||||||
|
amendPropTypes(documentation, path);
|
||||||
|
break;
|
||||||
|
case types.MemberExpression.name:
|
||||||
|
amendComposes(documentation, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = propTypeHandler;
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extractor for React documentation in JavaScript.
|
||||||
|
*/
|
||||||
|
var ReactDocumentationParser = require('./ReactDocumentationParser');
|
||||||
|
var parser = new ReactDocumentationParser();
|
||||||
|
|
||||||
|
parser.addHandler(
|
||||||
|
require('./handlers/propTypeHandler'),
|
||||||
|
'propTypes'
|
||||||
|
);
|
||||||
|
parser.addHandler(
|
||||||
|
require('./handlers/propDocBlockHandler'),
|
||||||
|
'propTypes'
|
||||||
|
);
|
||||||
|
parser.addHandler(
|
||||||
|
require('./handlers/defaultValueHandler'),
|
||||||
|
'getDefaultProps'
|
||||||
|
);
|
||||||
|
|
||||||
|
parser.addHandler(
|
||||||
|
require('./handlers/componentDocblockHandler')
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = parser;
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
|
@ -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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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 (pattern[prop] !== pattern[prop]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = match;
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "react-docgen",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Extract information from React components for documentation generation",
|
||||||
|
"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"
|
||||||
|
],
|
||||||
|
"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.2.2",
|
||||||
|
"react-tools": "^0.12.2"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"scriptPreprocessor": "./preprocessor",
|
||||||
|
"testPathDirs": ["lib"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
Loading…
Reference in New Issue