mirror of
https://github.com/status-im/react-native.git
synced 2025-01-12 02:24:23 +00:00
Merge branch 'master' into copyright-patch
This commit is contained in:
commit
fe0054f70f
@ -4,6 +4,7 @@ title: Getting Started
|
||||
layout: docs
|
||||
category: Quick Start
|
||||
permalink: docs/getting-started.html
|
||||
next: navigatorios
|
||||
---
|
||||
|
||||
|
||||
|
2
website/.gitignore
vendored
Normal file
2
website/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
src/react-native/docs/**
|
||||
core/metadata.js
|
@ -4,40 +4,11 @@
|
||||
*/
|
||||
|
||||
var React = require('React');
|
||||
var slugify = require('slugify');
|
||||
|
||||
var Header = React.createClass({
|
||||
slug: function(string) {
|
||||
// var accents = 'àáäâèéëêìíïîòóöôùúüûñç';
|
||||
var accents = '\u00e0\u00e1\u00e4\u00e2\u00e8' +
|
||||
'\u00e9\u00eb\u00ea\u00ec\u00ed\u00ef' +
|
||||
'\u00ee\u00f2\u00f3\u00f6\u00f4\u00f9' +
|
||||
'\u00fa\u00fc\u00fb\u00f1\u00e7';
|
||||
|
||||
var without = 'aaaaeeeeiiiioooouuuunc';
|
||||
|
||||
return string
|
||||
.toString()
|
||||
|
||||
// Handle uppercase characters
|
||||
.toLowerCase()
|
||||
|
||||
// Handle accentuated characters
|
||||
.replace(
|
||||
new RegExp('[' + accents + ']', 'g'),
|
||||
function (c) { return without.charAt(accents.indexOf(c)); })
|
||||
|
||||
// Dash special characters
|
||||
.replace(/[^a-z0-9]/g, '-')
|
||||
|
||||
// Compress multiple dash
|
||||
.replace(/-+/g, '-')
|
||||
|
||||
// Trim dashes
|
||||
.replace(/^-|-$/g, '');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var slug = this.slug(this.props.toSlug || this.props.children);
|
||||
var slug = slugify(this.props.toSlug || this.props.children);
|
||||
var H = React.DOM['h' + this.props.level];
|
||||
|
||||
return this.transferPropsTo(
|
||||
|
@ -53,7 +53,7 @@ var Site = React.createClass({
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-387204-10', 'facebook.github.io');
|
||||
ga('create', 'UA-41298772-2', 'facebook.github.io');
|
||||
ga('send', 'pageview');
|
||||
|
||||
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)
|
||||
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @generated
|
||||
* @providesModule Metadata
|
||||
*/
|
||||
module.exports = {
|
||||
"files": [
|
||||
{
|
||||
"id": "getting-started",
|
||||
"title": "Getting Started",
|
||||
"layout": "docs",
|
||||
"category": "Quick Start",
|
||||
"permalink": "docs/getting-started.html"
|
||||
}
|
||||
]
|
||||
};
|
35
website/core/slugify.js
Normal file
35
website/core/slugify.js
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @providesModule slugify
|
||||
*/
|
||||
|
||||
var slugify = function(string) {
|
||||
// var accents = 'àáäâèéëêìíïîòóöôùúüûñç';
|
||||
var accents = '\u00e0\u00e1\u00e4\u00e2\u00e8' +
|
||||
'\u00e9\u00eb\u00ea\u00ec\u00ed\u00ef' +
|
||||
'\u00ee\u00f2\u00f3\u00f6\u00f4\u00f9' +
|
||||
'\u00fa\u00fc\u00fb\u00f1\u00e7';
|
||||
|
||||
var without = 'aaaaeeeeiiiioooouuuunc';
|
||||
|
||||
return string
|
||||
.toString()
|
||||
|
||||
// Handle uppercase characters
|
||||
.toLowerCase()
|
||||
|
||||
// Handle accentuated characters
|
||||
.replace(
|
||||
new RegExp('[' + accents + ']', 'g'),
|
||||
function (c) { return without.charAt(accents.indexOf(c)); })
|
||||
|
||||
// Dash special characters
|
||||
.replace(/[^a-z0-9]/g, '-')
|
||||
|
||||
// Compress multiple dash
|
||||
.replace(/-+/g, '-')
|
||||
|
||||
// Trim dashes
|
||||
.replace(/^-|-$/g, '');
|
||||
};
|
||||
|
||||
module.exports = slugify;
|
1
website/react-docgen/.gitignore
vendored
Normal file
1
website/react-docgen/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
30
website/react-docgen/LICENSE
Normal file
30
website/react-docgen/LICENSE
Normal file
@ -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.
|
23
website/react-docgen/PATENTS
Normal file
23
website/react-docgen/PATENTS
Normal file
@ -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.
|
178
website/react-docgen/README.md
Normal file
178
website/react-docgen/README.md
Normal file
@ -0,0 +1,178 @@
|
||||
# react-docgen
|
||||
|
||||
`react-docgen` extracts information from React components with which
|
||||
you can generate documentation for those components.
|
||||
|
||||
It uses [recast][] to parse the provided files into an AST, looks for React
|
||||
component definitions, and inspects the `propTypes` and `getDefaultProps`
|
||||
declarations. The output is a JSON blob with the extracted information.
|
||||
|
||||
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-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.
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
The tool can also be used programmatically to extract component information:
|
||||
|
||||
```js
|
||||
var reactDocs = require('react-docgen');
|
||||
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>]
|
||||
["raw": string]
|
||||
},
|
||||
"required": boolean,
|
||||
"description": string,
|
||||
["defaultValue": {
|
||||
"value": number | string,
|
||||
"computed": boolean
|
||||
}]
|
||||
},
|
||||
...
|
||||
},
|
||||
["composes": <componentNames>]
|
||||
}
|
||||
```
|
||||
|
||||
[recast]: https://github.com/benjamn/recast
|
168
website/react-docgen/bin/react-docgen.js
vendored
Executable file
168
website/react-docgen/bin/react-docgen.js
vendored
Executable file
@ -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-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.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);
|
||||
}
|
||||
});
|
30
website/react-docgen/flow/recast.js
vendored
Normal file
30
website/react-docgen/flow/recast.js
vendored
Normal file
@ -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>;
|
||||
}
|
73
website/react-docgen/lib/Documentation.js
vendored
Normal file
73
website/react-docgen/lib/Documentation.js
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
type PropDescriptor = {
|
||||
type?: {
|
||||
name: string;
|
||||
value?: any;
|
||||
raw?: string;
|
||||
};
|
||||
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;
|
213
website/react-docgen/lib/ReactDocumentationParser.js
vendored
Normal file
213
website/react-docgen/lib/ReactDocumentationParser.js
vendored
Normal file
@ -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;
|
118
website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js
vendored
Normal file
118
website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js
vendored
Normal file
@ -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);
|
||||
});
|
||||
});
|
81
website/react-docgen/lib/handlers/__tests__/defaultValueHandler-test.js
vendored
Normal file
81
website/react-docgen/lib/handlers/__tests__/defaultValueHandler-test.js
vendored
Normal file
@ -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);
|
||||
});
|
||||
});
|
189
website/react-docgen/lib/handlers/__tests__/propDocblockHandler-test.js
vendored
Normal file
189
website/react-docgen/lib/handlers/__tests__/propDocblockHandler-test.js
vendored
Normal file
@ -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);
|
||||
});
|
||||
});
|
474
website/react-docgen/lib/handlers/__tests__/propTypeHandler-test.js
vendored
Normal file
474
website/react-docgen/lib/handlers/__tests__/propTypeHandler-test.js
vendored
Normal file
@ -0,0 +1,474 @@
|
||||
/*
|
||||
* 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('detects complex prop types', function() {
|
||||
var source = getSource([
|
||||
'{',
|
||||
' propTypes: {',
|
||||
' oneOf_prop: PropTypes.oneOf(["foo", "bar"]),',
|
||||
' oneOfType_prop:',
|
||||
' PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),',
|
||||
' oneOfType_custom_prop:',
|
||||
' PropTypes.oneOfType([xyz]),',
|
||||
' instanceOf_prop: PropTypes.instanceOf(Foo),',
|
||||
' arrayOf_prop: PropTypes.arrayOf(PropTypes.string),',
|
||||
' shape_prop:',
|
||||
' PropTypes.shape({foo: PropTypes.string, bar: PropTypes.bool}),',
|
||||
' shape_custom_prop:',
|
||||
' PropTypes.shape({foo: xyz})',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props:{
|
||||
oneOf_prop: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
value: [
|
||||
{value: '"foo"', computed: false},
|
||||
{value: '"bar"', computed: false}
|
||||
]
|
||||
},
|
||||
required: false
|
||||
},
|
||||
oneOfType_prop: {
|
||||
type: {
|
||||
name:'union',
|
||||
value: [
|
||||
{name: 'number'},
|
||||
{name: 'bool'}
|
||||
]
|
||||
},
|
||||
required: false
|
||||
},
|
||||
oneOfType_custom_prop: {
|
||||
type: {
|
||||
name:'union',
|
||||
value: [{
|
||||
name: 'custom',
|
||||
raw: 'xyz'
|
||||
}]
|
||||
},
|
||||
required: false
|
||||
},
|
||||
instanceOf_prop: {
|
||||
type: {
|
||||
name: 'instance',
|
||||
value: 'Foo'
|
||||
},
|
||||
required: false
|
||||
},
|
||||
arrayOf_prop: {
|
||||
type: {
|
||||
name: 'arrayof',
|
||||
value: {name: 'string'}
|
||||
},
|
||||
required: false
|
||||
},
|
||||
shape_prop: {
|
||||
type: {
|
||||
name: 'shape',
|
||||
value: {
|
||||
foo: {name: 'string'},
|
||||
bar: {name: 'bool'}
|
||||
}
|
||||
},
|
||||
required: false
|
||||
},
|
||||
shape_custom_prop: {
|
||||
type: {
|
||||
name: 'shape',
|
||||
value: {
|
||||
foo: {
|
||||
name: 'custom',
|
||||
raw: 'xyz'
|
||||
},
|
||||
}
|
||||
},
|
||||
required: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('resolves variables to their values', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var PropTypes = React.PropTypes;',
|
||||
'var shape = {bar: PropTypes.string};',
|
||||
'var Component = React.createClass({',
|
||||
' propTypes: {',
|
||||
' foo: PropTypes.shape(shape)',
|
||||
' }',
|
||||
'});',
|
||||
'module.exports = Component;'
|
||||
].join('\n');
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
foo: {
|
||||
type: {
|
||||
name: 'shape',
|
||||
value: {
|
||||
bar: {name: 'string'}
|
||||
}
|
||||
},
|
||||
required: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
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() {},',
|
||||
' custom_prop2: () => {}',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n'));
|
||||
|
||||
var expectedResult = {
|
||||
description: '',
|
||||
props: {
|
||||
custom_prop: {
|
||||
type: {
|
||||
name: 'custom',
|
||||
raw: 'function() {}'
|
||||
},
|
||||
required: false
|
||||
},
|
||||
custom_prop2: {
|
||||
type: {
|
||||
name: 'custom',
|
||||
raw: '() => {}'
|
||||
},
|
||||
required: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = parser.parseSource(source);
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('only considers definitions from React or ReactPropTypes', function() {
|
||||
var source = [
|
||||
'var React = require("React");',
|
||||
'var PropTypes = React.PropTypes;',
|
||||
'var Prop = require("Foo");',
|
||||
'var Component = React.createClass({',
|
||||
' 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',
|
||||
raw: 'Prop.bool.isRequired'
|
||||
},
|
||||
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);
|
||||
});
|
||||
});
|
39
website/react-docgen/lib/handlers/componentDocblockHandler.js
vendored
Normal file
39
website/react-docgen/lib/handlers/componentDocblockHandler.js
vendored
Normal file
@ -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;
|
77
website/react-docgen/lib/handlers/defaultValueHandler.js
vendored
Normal file
77
website/react-docgen/lib/handlers/defaultValueHandler.js
vendored
Normal file
@ -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 = recast.print(path).code;
|
||||
}
|
||||
if (typeof defaultValue !== 'undefined') {
|
||||
return {
|
||||
value: defaultValue,
|
||||
computed: types.CallExpression.check(node) ||
|
||||
types.MemberExpression.check(node) ||
|
||||
types.Identifier.check(node)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function defaultValueHandler(documentation: Documentation, path: NodePath) {
|
||||
if (!types.FunctionExpression.check(path.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the value that is returned from the function and process it if it is
|
||||
// an object literal.
|
||||
var objectExpressionPath;
|
||||
visit(path.get('body'), {
|
||||
visitFunction: () => false,
|
||||
visitReturnStatement: function(path) {
|
||||
var resolvedPath = resolveToValue(path.get('argument'));
|
||||
if (types.ObjectExpression.check(resolvedPath.node)) {
|
||||
objectExpressionPath = resolvedPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (objectExpressionPath) {
|
||||
objectExpressionPath.get('properties').each(function(propertyPath) {
|
||||
var propDescriptor = documentation.getPropDescriptor(
|
||||
getPropertyName(propertyPath)
|
||||
);
|
||||
var defaultValue = getDefaultValue(propertyPath.get('value'));
|
||||
if (defaultValue) {
|
||||
propDescriptor.defaultValue = defaultValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = defaultValueHandler;
|
38
website/react-docgen/lib/handlers/propDocBlockHandler.js
vendored
Normal file
38
website/react-docgen/lib/handlers/propDocBlockHandler.js
vendored
Normal file
@ -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;
|
259
website/react-docgen/lib/handlers/propTypeHandler.js
vendored
Normal file
259
website/react-docgen/lib/handlers/propTypeHandler.js
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 recast = require('recast');
|
||||
var resolveToModule = require('../utils/resolveToModule');
|
||||
var resolveToValue = require('../utils/resolveToValue');
|
||||
var types = recast.types.namedTypes;
|
||||
|
||||
var simplePropTypes = {
|
||||
array: 1,
|
||||
bool: 1,
|
||||
func: 1,
|
||||
number: 1,
|
||||
object: 1,
|
||||
string: 1,
|
||||
any: 1,
|
||||
element: 1,
|
||||
node: 1
|
||||
};
|
||||
|
||||
function isPropTypesExpression(path) {
|
||||
var moduleName = resolveToModule(path);
|
||||
if (moduleName) {
|
||||
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.Function.check(node) || !isPropTypesExpression(path)) {
|
||||
return {
|
||||
name: 'custom',
|
||||
raw: recast.print(path).code
|
||||
};
|
||||
}
|
||||
|
||||
var expressionParts = [];
|
||||
|
||||
if (types.MemberExpression.check(node)) {
|
||||
// React.PropTypes.something.isRequired
|
||||
if (isRequired(path)) {
|
||||
path = path.get('object');
|
||||
node = path.node;
|
||||
}
|
||||
// React.PropTypes.something
|
||||
expressionParts = expressionTo.Array(path);
|
||||
}
|
||||
if (types.CallExpression.check(node)) {
|
||||
// React.PropTypes.something()
|
||||
expressionParts = expressionTo.Array(path.get('callee'));
|
||||
}
|
||||
|
||||
// React.PropTypes.something -> something
|
||||
var propType = expressionParts.pop();
|
||||
var type;
|
||||
if (propType in propTypes) {
|
||||
type = propTypes[propType](path);
|
||||
} else {
|
||||
type = {name: (propType in simplePropTypes) ? propType : 'unknown'};
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true of the prop is required, according to its type defintion
|
||||
*/
|
||||
function isRequired(path) {
|
||||
if (types.MemberExpression.check(path.node)) {
|
||||
var expressionParts = expressionTo.Array(path);
|
||||
if (expressionParts[expressionParts.length - 1] === 'isRequired') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
39
website/react-docgen/lib/main.js
vendored
Normal file
39
website/react-docgen/lib/main.js
vendored
Normal file
@ -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;
|
40
website/react-docgen/lib/utils/__tests__/docblock-test.js
vendored
Normal file
40
website/react-docgen/lib/utils/__tests__/docblock-test.js
vendored
Normal file
@ -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'});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
60
website/react-docgen/lib/utils/docblock.js
vendored
Normal file
60
website/react-docgen/lib/utils/docblock.js
vendored
Normal file
@ -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;
|
80
website/react-docgen/lib/utils/expressionTo.js
vendored
Normal file
80
website/react-docgen/lib/utils/expressionTo.js
vendored
Normal file
@ -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;
|
34
website/react-docgen/lib/utils/getNameOrValue.js
vendored
Normal file
34
website/react-docgen/lib/utils/getNameOrValue.js
vendored
Normal file
@ -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;
|
32
website/react-docgen/lib/utils/getPropertyName.js
vendored
Normal file
32
website/react-docgen/lib/utils/getPropertyName.js
vendored
Normal file
@ -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;
|
28
website/react-docgen/lib/utils/isReactModuleName.js
vendored
Normal file
28
website/react-docgen/lib/utils/isReactModuleName.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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;
|
40
website/react-docgen/lib/utils/match.js
vendored
Normal file
40
website/react-docgen/lib/utils/match.js
vendored
Normal file
@ -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;
|
54
website/react-docgen/lib/utils/resolveToModule.js
vendored
Normal file
54
website/react-docgen/lib/utils/resolveToModule.js
vendored
Normal file
@ -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;
|
46
website/react-docgen/lib/utils/resolveToValue.js
vendored
Normal file
46
website/react-docgen/lib/utils/resolveToValue.js
vendored
Normal file
@ -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;
|
35
website/react-docgen/package.json
Normal file
35
website/react-docgen/package.json
Normal file
@ -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"]
|
||||
}
|
||||
}
|
9
website/react-docgen/preprocessor.js
vendored
Normal file
9
website/react-docgen/preprocessor.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
var reactTools = require('react-tools');
|
||||
|
||||
function process(source) {
|
||||
return reactTools.transform(source, {harmony: true, stripTypes: true});
|
||||
}
|
||||
|
||||
exports.process = process;
|
@ -3,6 +3,7 @@ var glob = require('glob');
|
||||
var mkdirp = require('mkdirp');
|
||||
var optimist = require('optimist');
|
||||
var path = require('path');
|
||||
var extractDocs = require('./extractDocs');
|
||||
var argv = optimist.argv;
|
||||
|
||||
function splitHeader(content) {
|
||||
@ -28,89 +29,93 @@ function backtickify(str) {
|
||||
function execute() {
|
||||
var MD_DIR = '../docs/';
|
||||
|
||||
glob('src/react-native/docs/*.*', function(er, files) {
|
||||
files.forEach(function(file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
} catch(e) {
|
||||
/* seriously, unlink throws when the file doesn't exist :( */
|
||||
}
|
||||
});
|
||||
var files = glob.sync('src/react-native/docs/*.*')
|
||||
files.forEach(function(file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
} catch(e) {
|
||||
/* seriously, unlink throws when the file doesn't exist :( */
|
||||
}
|
||||
});
|
||||
|
||||
var metadatas = {
|
||||
files: [],
|
||||
};
|
||||
|
||||
glob(MD_DIR + '**/*.*', function (er, files) {
|
||||
files.forEach(function(file) {
|
||||
var extension = path.extname(file);
|
||||
if (extension === '.md' || extension === '.markdown') {
|
||||
var content = fs.readFileSync(file, {encoding: 'utf8'});
|
||||
var metadata = {};
|
||||
function handleMarkdown(content) {
|
||||
var metadata = {};
|
||||
|
||||
// Extract markdown metadata header
|
||||
var both = splitHeader(content);
|
||||
var lines = both.header.split('\n');
|
||||
for (var i = 0; i < lines.length - 1; ++i) {
|
||||
var keyvalue = lines[i].split(':');
|
||||
var key = keyvalue[0].trim();
|
||||
var value = keyvalue.slice(1).join(':').trim();
|
||||
// Handle the case where you have "Community #10"
|
||||
try { value = JSON.parse(value); } catch(e) { }
|
||||
metadata[key] = value;
|
||||
}
|
||||
metadatas.files.push(metadata);
|
||||
// Extract markdown metadata header
|
||||
var both = splitHeader(content);
|
||||
var lines = both.header.split('\n');
|
||||
for (var i = 0; i < lines.length - 1; ++i) {
|
||||
var keyvalue = lines[i].split(':');
|
||||
var key = keyvalue[0].trim();
|
||||
var value = keyvalue.slice(1).join(':').trim();
|
||||
// Handle the case where you have "Community #10"
|
||||
try { value = JSON.parse(value); } catch(e) { }
|
||||
metadata[key] = value;
|
||||
}
|
||||
metadatas.files.push(metadata);
|
||||
|
||||
if (metadata.permalink.match(/^https?:/)) {
|
||||
return;
|
||||
}
|
||||
if (metadata.permalink.match(/^https?:/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a dummy .js version that just calls the associated layout
|
||||
var layout = metadata.layout[0].toUpperCase() + metadata.layout.substr(1) + 'Layout';
|
||||
// Create a dummy .js version that just calls the associated layout
|
||||
var layout = metadata.layout[0].toUpperCase() + metadata.layout.substr(1) + 'Layout';
|
||||
|
||||
var content = (
|
||||
'/**\n' +
|
||||
' * @generated\n' +
|
||||
' * @jsx React.DOM\n' +
|
||||
' */\n' +
|
||||
'var React = require("React");\n' +
|
||||
'var layout = require("' + layout + '");\n' +
|
||||
'var content = ' + backtickify(both.content) + '\n' +
|
||||
'var Post = React.createClass({\n' +
|
||||
' render: function() {\n' +
|
||||
' return layout({metadata: ' + JSON.stringify(metadata) + '}, content);\n' +
|
||||
' }\n' +
|
||||
'});\n' +
|
||||
// TODO: Use React statics after upgrading React
|
||||
'Post.content = content;\n' +
|
||||
'module.exports = Post;\n'
|
||||
);
|
||||
|
||||
var targetFile = 'src/react-native/' + metadata.permalink.replace(/\.html$/, '.js');
|
||||
mkdirp.sync(targetFile.replace(new RegExp('/[^/]*$'), ''));
|
||||
fs.writeFileSync(targetFile, content);
|
||||
}
|
||||
|
||||
if (extension === '.json') {
|
||||
var content = fs.readFileSync(file, {encoding: 'utf8'});
|
||||
metadatas[path.basename(file, '.json')] = JSON.parse(content);
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
'core/metadata.js',
|
||||
var content = (
|
||||
'/**\n' +
|
||||
' * @generated\n' +
|
||||
' * @providesModule Metadata\n' +
|
||||
' * @jsx React.DOM\n' +
|
||||
' */\n' +
|
||||
'module.exports = ' + JSON.stringify(metadatas, null, 2) + ';'
|
||||
'var React = require("React");\n' +
|
||||
'var layout = require("' + layout + '");\n' +
|
||||
'var content = ' + backtickify(both.content) + '\n' +
|
||||
'var Post = React.createClass({\n' +
|
||||
' render: function() {\n' +
|
||||
' return layout({metadata: ' + JSON.stringify(metadata) + '}, content);\n' +
|
||||
' }\n' +
|
||||
'});\n' +
|
||||
// TODO: Use React statics after upgrading React
|
||||
'Post.content = content;\n' +
|
||||
'module.exports = Post;\n'
|
||||
);
|
||||
|
||||
var targetFile = 'src/react-native/' + metadata.permalink.replace(/\.html$/, '.js');
|
||||
mkdirp.sync(targetFile.replace(new RegExp('/[^/]*$'), ''));
|
||||
fs.writeFileSync(targetFile, content);
|
||||
}
|
||||
|
||||
extractDocs().forEach(handleMarkdown);
|
||||
|
||||
var files = glob.sync(MD_DIR + '**/*.*');
|
||||
files.forEach(function(file) {
|
||||
var extension = path.extname(file);
|
||||
if (extension === '.md' || extension === '.markdown') {
|
||||
var content = fs.readFileSync(file, {encoding: 'utf8'});
|
||||
handleMarkdown(content);
|
||||
}
|
||||
|
||||
if (extension === '.json') {
|
||||
var content = fs.readFileSync(file, {encoding: 'utf8'});
|
||||
metadatas[path.basename(file, '.json')] = JSON.parse(content);
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
'core/metadata.js',
|
||||
'/**\n' +
|
||||
' * @generated\n' +
|
||||
' * @providesModule Metadata\n' +
|
||||
' */\n' +
|
||||
'module.exports = ' + JSON.stringify(metadatas, null, 2) + ';'
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.convert) {
|
||||
console.log('convert!')
|
||||
console.log('convert!');
|
||||
execute();
|
||||
}
|
||||
|
||||
|
53
website/server/extractDocs.js
Normal file
53
website/server/extractDocs.js
Normal file
@ -0,0 +1,53 @@
|
||||
var docs = require('../react-docgen');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var slugify = require('../core/slugify');
|
||||
|
||||
function getNameFromPath(filepath) {
|
||||
var ext = null;
|
||||
while (ext = path.extname(filepath)) {
|
||||
filepath = path.basename(filepath, ext);
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
function docsToMarkdown(filepath, i) {
|
||||
var json = docs.parseSource(fs.readFileSync(filepath));
|
||||
var componentName = getNameFromPath(filepath);
|
||||
|
||||
var res = [
|
||||
'---',
|
||||
'id: ' + slugify(componentName),
|
||||
'title: ' + componentName,
|
||||
'layout: docs',
|
||||
'category: Components',
|
||||
'permalink: docs/' + slugify(componentName) + '.html',
|
||||
components[i + 1] && ('next: ' + slugify(getNameFromPath(components[i + 1]))),
|
||||
'---',
|
||||
' ',
|
||||
json.description,
|
||||
' ',
|
||||
'# Props',
|
||||
'```',
|
||||
JSON.stringify(json.props, null, 2),
|
||||
'```',
|
||||
].filter(function(line) { return line; }).join('\n');
|
||||
return res;
|
||||
}
|
||||
|
||||
var components = [
|
||||
'../Libraries/Components/Navigation/NavigatorIOS.ios.js',
|
||||
'../Libraries/Components/Image/Image.ios.js',
|
||||
'../Libraries/Components/ListView/ListView.js',
|
||||
'../Libraries/Components/Navigation/NavigatorIOS.ios.js',
|
||||
'../Libraries/Components/ScrollView/ScrollView.ios.js',
|
||||
'../Libraries/Components/Text/Text.js',
|
||||
'../Libraries/Components/TextInput/TextInput.ios.js',
|
||||
'../Libraries/Components/Touchable/TouchableHighlight.js',
|
||||
'../Libraries/Components/Touchable/TouchableWithoutFeedback.js',
|
||||
// '../Libraries/Components/View/View.js',
|
||||
];
|
||||
|
||||
module.exports = function() {
|
||||
return components.map(docsToMarkdown);
|
||||
};
|
@ -26,7 +26,7 @@ var queue = (function() {
|
||||
is_executing = true;
|
||||
fn(function() {
|
||||
is_executing = false;
|
||||
execute()
|
||||
execute();
|
||||
});
|
||||
}
|
||||
return {push: push};
|
||||
|
85
website/src/react-native/docs/getting-started.js
vendored
85
website/src/react-native/docs/getting-started.js
vendored
@ -1,85 +0,0 @@
|
||||
/**
|
||||
* @generated
|
||||
* @jsx React.DOM
|
||||
*/
|
||||
var React = require("React");
|
||||
var layout = require("DocsLayout");
|
||||
var content = `
|
||||
|
||||
Our first React Native implementation is \`ReactKit\`, targeting iOS. We are also
|
||||
working on an Android implementation which we will release later. \`ReactKit\`
|
||||
apps are built using the [React JS](https://github.com/facebook/react) framework, and render directly to
|
||||
native UIKit elements using a fully asynchronous architecture. There is no
|
||||
browser and no HTML. We have picked what we think is the best set of features
|
||||
from these and other technologies to build what we hope to become the best
|
||||
product development framework available, with an emphasis on iteration speed,
|
||||
developer delight, continuity of technology, and absolutely beautiful and fast
|
||||
products with no compromises in quality or capability.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. OS X - This repo only contains the iOS implementation right now, and Xcode only runs on Mac.
|
||||
2. New to Xcode? [Download it](https://developer.apple.com/xcode/downloads/) from the Mac App Store.
|
||||
3. [Homebrew](http://brew.sh/) is the recommended way to install node, watchman, and flow.
|
||||
4. New to node or npm? \`brew install node\`
|
||||
5. We recommend installing [watchman](https://facebook.github.io/watchman/docs/install.html), otherwise you might hit a node file watching bug. \`brew install watchman\`
|
||||
6. If you want to use [flow](http://www.flowtype.org), \`brew install flow\`
|
||||
|
||||
## Quick start
|
||||
|
||||
Get up and running with our Movies sample app:
|
||||
|
||||
1. Once you have the repo cloned and met all the requirements above, start the
|
||||
packager that will transform your JS code on-the-fly:
|
||||
|
||||
\`\`\`
|
||||
npm install
|
||||
npm start
|
||||
\`\`\`
|
||||
2. Open the \`Examples/Movies/Movies.xcodeproj\` project in Xcode.
|
||||
3. Make sure the target is set to \`Movies\` and that you have an iOS simulator
|
||||
selected to run the app.
|
||||
4. Build and run the project with the Xcode run button.
|
||||
|
||||
You should now see the Movies app running on your iOS simulator.
|
||||
Congratulations! You've just successfully run your first React Native app.
|
||||
|
||||
Now try editing a JavaScript file and viewing your changes. Let's change the
|
||||
movie search placeholder text:
|
||||
|
||||
1. Open the \`Examples/Movies/SearchScreen.js\` file in your favorite JavaScript
|
||||
editor.
|
||||
2. Look for the current search placeholder text and change it to "Search for an
|
||||
awesome movie...".
|
||||
3. Hit cmd+R ([twice](http://openradar.appspot.com/19613391)) in your iOS simulator to reload the app and see your change.
|
||||
If you don't immediately see your changes, try restarting your app within Xcode.
|
||||
|
||||
Feel free to browse the Movies sample files and customize various properties to
|
||||
get familiar with the codebase and React Native.
|
||||
|
||||
Also check out the UI Component Explorer for more sample code:
|
||||
\`Examples/UIExplorer/UIExplorer.xcodeproj\`. **Make sure to close the Movies
|
||||
project first - Xcode will break if you have two projects open that reference
|
||||
the same library.**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
+ Xcode will break if you have two examples open at the same time.
|
||||
+ If \`npm start\` fails with log spew like:
|
||||
\`\`\`
|
||||
2015-02-02 10:56 node[24294] (FSEvents.framework) FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() => (null) (-21)
|
||||
\`\`\`
|
||||
then you've hit the node file watching bug - \`brew install watchman\` should fix the issue.
|
||||
+ Jest testing does not yet work on node versions after 0.10.x.
|
||||
+ You can verify the packager is working by loading the [bundle](http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle) in your browser and
|
||||
inspecting the contents.
|
||||
|
||||
Please report any other issues you encounter so we can fix them ASAP.
|
||||
`
|
||||
var Post = React.createClass({
|
||||
render: function() {
|
||||
return layout({metadata: {"id":"getting-started","title":"Getting Started","layout":"docs","category":"Quick Start","permalink":"docs/getting-started.html"}}, content);
|
||||
}
|
||||
});
|
||||
Post.content = content;
|
||||
module.exports = Post;
|
2
website/src/react-native/support.js
vendored
2
website/src/react-native/support.js
vendored
@ -32,7 +32,7 @@ var support = React.createClass({
|
||||
<H2>Twitter</H2>
|
||||
<p><a href="https://twitter.com/search?q=%23reactnative"><strong>#reactnative</strong> hash tag on Twitter</a> is used to keep up with the latest React Native news.</p>
|
||||
|
||||
<p><center><a className="twitter-timeline" data-dnt="true" data-chrome="nofooter noheader transparent" href="https://twitter.com/search?q=%23react+native" data-widget-id="464145350849085440"></a></center></p>
|
||||
<p><center><a className="twitter-timeline" data-dnt="true" data-chrome="nofooter noheader transparent" href="https://twitter.com/search?q=%23reactnative" data-widget-id="565960513457098753"></a></center></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user