Skip to content

Commit

Permalink
Update react-docgen
Browse files Browse the repository at this point in the history
  • Loading branch information
fkling committed Mar 3, 2015
1 parent 7b51a09 commit f77d9f8
Show file tree
Hide file tree
Showing 39 changed files with 1,785 additions and 774 deletions.
43 changes: 43 additions & 0 deletions website/react-docgen/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Contributing to react-docgen
We want to make contributing to this project as easy and transparent as
possible.

## Our Development Process
The majority of development on react-docgen will occur through GitHub. Accordingly,
the process for contributing will follow standard GitHub protocol.

## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints and typechecks.
6. If you haven't already, complete the Contributor License Agreement ("CLA").

## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.

Complete your CLA here: <https://code.facebook.com/cla>

## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.

Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.

## Coding Style
* Use semicolons;
* Commas last,
* 2 spaces for indentation (no tabs)
* Prefer `'` over `"`
* `"use strict";`
* 80 character line length
* "Attractive"

## License
By contributing to react-docgen, you agree that your contributions will be licensed
under its BSD license.
59 changes: 47 additions & 12 deletions website/react-docgen/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
# react-docgen

`react-docgen` extracts information from React components with which
you can generate documentation for those components.
`react-docgen` is a CLI and toolbox to help extracting information from React components, and generate documentation from it.

It uses [recast][] to parse the provided files into an AST, looks for React
component definitions, and inspects the `propTypes` and `getDefaultProps`
declarations. The output is a JSON blob with the extracted information.
It uses [recast][] to parse the source into an AST and provides methods to process this AST to extract the desired information. The output / return value is a JSON blob / JavaScript object.

Note that component definitions must follow certain guidelines in order to be
analyzable by this tool. We will work towards less strict guidelines, but there
is a limit to what is statically analyzable.
It provides a default implementation for React components defined via `React.createClass`. These component definitions must follow certain guidelines in order to be analyzable (see below for more info)

## Install

Expand Down Expand Up @@ -41,22 +36,62 @@ Extract meta information from React components.
If a directory is passed, it is recursively traversed.
```

By default, `react-docgen` will look for the exported component created through `React.createClass` in each file. Have a look below for how to customize this behavior.

## API

The tool can also be used programmatically to extract component information:
The tool can be used programmatically to extract component information and customize the extraction process:

```js
var reactDocs = require('react-docgen');
var componentInfo reactDocs.parseSource(src);
var componentInfo = reactDocs.parse(src);
```

As with the CLI, this will look for the exported component created through `React.createClass` in the provided source. The whole process of analyzing the source code is separated into two parts:

- Locating/finding the nodes in the AST which define the component
- Extracting information from those nodes

`parse` accepts more arguments with which this behavior can be customized.

### parse(source \[, resolver \[, handlers\]\])

| Parameter | Type | Description |
| -------------- | ------ | --------------- |
| source | string | The source text |
| resolver | function | A function of the form `(ast: ASTNode, recast: Object) => (NodePath|Array<NodePath>)`. Given an AST and a reference to recast, it returns an (array of) NodePath which represents the component definition. |
| handlers | Array\<function\> | An array of functions of the form `(documentation: Documentation, definition: NodePath) => void`. Each function is called with a `Documentation` object and a reference to the component definition as returned by `resolver`. Handlers extract relevant information from the definition and augment `documentation`.


#### resolver

The resolver's task is to extract those parts from the source code which the handlers can analyze. For example, the `findExportedReactCreateClassCall` resolver inspects the AST to find

```js
var Component = React.createClass(<def>);
module.exports = Component;
```

## Guidelines
and returns the ObjectExpression to which `<def>` resolves.

`findAllReactCreateClassCalls` works similarly, but simply finds all `React.createClass` calls, not only the one that creates the exported component.

This makes it easy, together with the utility methods created to analyze the AST, to introduce new or custom resolver methods. For example, a resolver could look for plain ObjectExpressions with a `render` method or `class Component extends React.Component` instead (**note:** a default resolver for `class` based react components is planned).

#### handlers

Handlers do the actual work and extract the desired information from the result the resolver returned. Like the resolver, they try to delegate as much work as possible to the reusable utility functions.

For example, while the `propTypesHandler` expects the prop types definition to be an ObjectExpression and be located inside an ObjectExpression under the property name `propTypes`, most of the work is actually performed by the `getPropType` utility function.

## Guidelines for default resolvers and handlers

- Modules have to export a single component, and only that component is
analyzed.
- The component definition must be an object literal.
- `propTypes` must be an object literal or resolve to an object literal in the
same file.
- The `return` statement in `getDefaultProps` must consist of an object literal.
- The `return` statement in `getDefaultProps` must contain an object literal.

## Example

Expand Down
6 changes: 3 additions & 3 deletions website/react-docgen/bin/react-docgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ if (paths.length === 0) {
source += chunk;
});
process.stdin.on('end', function () {
exitWithResult(parser.parseSource(source));
exitWithResult(parser.parse(source));
});
}

Expand All @@ -112,7 +112,7 @@ function traverseDir(path, result, done) {
exitWithError(error);
}
try {
result[filename] = parser.parseSource(content);
result[filename] = parser.parse(content);
} catch(error) {
writeError(error, path);
}
Expand Down Expand Up @@ -143,7 +143,7 @@ async.eachSeries(paths, function(path, done) {
}
else {
try {
result[path] = parser.parseSource(fs.readFileSync(path));
result[path] = parser.parse(fs.readFileSync(path));
} catch(error) {
writeError(error, path);
}
Expand Down
35 changes: 35 additions & 0 deletions website/react-docgen/flow/react-docgen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

type PropTypeDescriptor = {
name: string;
value?: any;
raw?: string;
};

type PropDescriptor = {
type?: PropTypeDescriptor;
required?: boolean;
defaultValue?: any;
description?: string;
};

declare class Documentation {
addComposes(moduleName: string): void;
getDescription(): string;
setDescription(description: string): void;
getPropDescriptor(propName: string): PropDescriptor;
toObject(): Object;
}


type Handler = (documentation: Documentation, path: NodePath) => void;
type Resolver =
(node: ASTNode, recast: Recast) => (NodePath|Array<NodePath>|void);
5 changes: 5 additions & 0 deletions website/react-docgen/flow/recast.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ declare class NodePath {
each(f: (p: NodePath) => void): void;
map<T>(f: (p: NodePath) => T): Array<T>;
}

type Recast = {
parse: (src: string) => ASTNode;
print: (path: NodePath) => {code: string};
};
11 changes: 0 additions & 11 deletions website/react-docgen/lib/Documentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@
*/
"use strict";

type PropDescriptor = {
type?: {
name: string;
value?: any;
raw?: string;
};
required?: boolean;
defaultValue?: any;
description?: string;
};

class Documentation {
_props: Object;
_description: string;
Expand Down
28 changes: 28 additions & 0 deletions website/react-docgen/lib/__mocks__/Documentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

function Documentation() {
return {
description: '',
composes: [],
descriptors: {},
getPropDescriptor(name) {
return this.descriptors[name] || (this.descriptors[name] = {});
},
addComposes(name) {
this.composes.push(name);
},
setDescription(descr) {
this.description = descr;
}
};
}

module.exports = Documentation;
75 changes: 75 additions & 0 deletions website/react-docgen/lib/__tests__/main-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

"use strict";

jest.autoMockOff();

var source = [
'var React = require("React");',
'var PropTypes = React.PropTypes;',
'/**',
' * Example component description',
' */',
'var Component = React.createClass({',
' propTypes: {',
' /**',
' * Example prop description',
' */',
' foo: PropTypes.bool',
' },',
' getDefaultProps: function() {',
' return {',
' foo: true',
' };',
' }',
'});',
'module.exports = Component;'
].join('\n');

describe('main', function() {
var utils;
var docgen;

beforeEach(function() {
utils = require('../../tests/utils');
docgen = require('../main');
});

it('parses with default resolver/handlers', function() {
var docs = docgen.parse(source);
expect(docs).toEqual({
description: 'Example component description',
props: {
foo: {
type: {
name: 'bool'
},
defaultValue: {
computed: false,
value: 'true'
},
description: 'Example prop description',
required: false
}
}
});
});

it('parses with custom handlers', function() {
var docs = docgen.parse(source, null, [
docgen.handlers.componentDocblockHandler,
]);
expect(docs).toEqual({
description: 'Example component description',
props: {}
});
});
});
52 changes: 52 additions & 0 deletions website/react-docgen/lib/__tests__/parse-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

"use strict";

jest.autoMockOff();

describe('parse', function() {
var utils;
var parse;

beforeEach(function() {
utils = require('../../tests/utils');
parse = require('../parse');
});

function pathFromSource(source) {
return utils.parse(source).get('body', 0, 'expression');
}

it('allows custom component definition resolvers', function() {
var path = pathFromSource('({foo: "bar"})');
var resolver = jest.genMockFunction().mockReturnValue(path);
var handler = jest.genMockFunction();
parse('', resolver, [handler]);

expect(resolver).toBeCalled();
expect(handler.mock.calls[0][1]).toBe(path);
});

it('errors if component definition is not found', function() {
var resolver = jest.genMockFunction();
expect(function() {
parse('', resolver);
}).toThrow(parse.ERROR_MISSING_DEFINITION);
expect(resolver).toBeCalled();

handler = jest.genMockFunction().mockReturnValue([]);
expect(function() {
parse('', resolver);
}).toThrow(parse.ERROR_MISSING_DEFINITION);
expect(resolver).toBeCalled();
});

});
Loading

0 comments on commit f77d9f8

Please sign in to comment.