Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove recast and use ast-types directly #349

Merged
merged 8 commits into from May 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.js
Expand Up @@ -16,7 +16,7 @@ module.exports = {
globals: {
ASTNode: true,
NodePath: true,
Recast: true,
$Exact: true,
},
overrides: [
{
Expand All @@ -28,7 +28,7 @@ module.exports = {
},
},
{
files: 'src/**/__tests__/*-test.js',
files: '@(src|bin)/**/__tests__/*-test.js',
env: { jest: true },
},
],
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -2,7 +2,7 @@

`react-docgen` is a CLI and toolbox to help extracting information from [React][] components, and generate documentation from it.

It uses [recast][] and [@babel/parser][] to parse the source into an AST and provides methods to process this AST to extract the desired information. The output / return value is a JSON blob / JavaScript object.
It uses [ast-types][] and [@babel/parser][] to parse the source into an AST and provides methods to process this AST to extract the desired information. The output / return value is a JSON blob / JavaScript object.

It provides a default implementation for React components defined via
`React.createClass`, [ES2015 class definitions][classes] or functions
Expand Down Expand Up @@ -82,8 +82,8 @@ As with the CLI, this will look for the exported component created through `Reac
| 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 | function | A function of the form `(ast: ASTNode, parser: Parser) => (NodePath|Array<NodePath>)`. Given an AST and a reference to the parser, 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, parser: Parser) => 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`. |
| opt

#### options
Expand Down Expand Up @@ -412,6 +412,6 @@ The structure of the JSON blob / JavaScript object is as follows:
[react]: http://facebook.github.io/react/
[flow]: http://flowtype.org/
[typescript]: http://typescriptlang.org/
[recast]: https://github.com/benjamn/recast
[ast-types]: https://github.com/benjamn/ast-types
[@babel/parser]: https://github.com/babel/babel/tree/master/packages/babel-parser
[classes]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
249 changes: 249 additions & 0 deletions benchmark/fixtures/CircularProgress.js
@@ -0,0 +1,249 @@
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { chainPropTypes } from '@material-ui/utils';
import withStyles from '../styles/withStyles';
import { capitalize } from '../utils/helpers';

const SIZE = 44;

function getRelativeValue(value, min, max) {
const clampedValue = Math.min(Math.max(min, value), max);
return (clampedValue - min) / (max - min);
}

function easeOut(t) {
t = getRelativeValue(t, 0, 1);
// https://gist.github.com/gre/1650294
t = (t -= 1) * t * t + 1;
return t;
}

function easeIn(t) {
return t * t;
}

export const styles = theme => ({
/* Styles applied to the root element. */
root: {
display: 'inline-block',
lineHeight: 1, // Keep the progress centered
},
/* Styles applied to the root element if `variant="static"`. */
static: {
transition: theme.transitions.create('transform'),
},
/* Styles applied to the root element if `variant="indeterminate"`. */
indeterminate: {
animation: 'mui-progress-circular-rotate 1.4s linear infinite',
// Backward compatible logic between JSS v9 and v10.
// To remove with the release of Material-UI v4
animationName: '$mui-progress-circular-rotate',
},
/* Styles applied to the root element if `color="primary"`. */
colorPrimary: {
color: theme.palette.primary.main,
},
/* Styles applied to the root element if `color="secondary"`. */
colorSecondary: {
color: theme.palette.secondary.main,
},
/* Styles applied to the `svg` element. */
svg: {},
/* Styles applied to the `circle` svg path. */
circle: {
stroke: 'currentColor',
// Use butt to follow the specification, by chance, it's already the default CSS value.
// strokeLinecap: 'butt',
},
/* Styles applied to the `circle` svg path if `variant="static"`. */
circleStatic: {
transition: theme.transitions.create('stroke-dashoffset'),
},
/* Styles applied to the `circle` svg path if `variant="indeterminate"`. */
circleIndeterminate: {
animation: 'mui-progress-circular-dash 1.4s ease-in-out infinite',
// Backward compatible logic between JSS v9 and v10.
// To remove with the release of Material-UI v4
animationName: '$mui-progress-circular-dash',
// Some default value that looks fine waiting for the animation to kicks in.
strokeDasharray: '80px, 200px',
strokeDashoffset: '0px', // Add the unit to fix a Edge 16 and below bug.
},
'@keyframes mui-progress-circular-rotate': {
'100%': {
transform: 'rotate(360deg)',
},
},
'@keyframes mui-progress-circular-dash': {
'0%': {
strokeDasharray: '1px, 200px',
strokeDashoffset: '0px',
},
'50%': {
strokeDasharray: '100px, 200px',
strokeDashoffset: '-15px',
},
'100%': {
strokeDasharray: '100px, 200px',
strokeDashoffset: '-125px',
},
},
/* Styles applied to the `circle` svg path if `disableShrink={true}`. */
circleDisableShrink: {
animation: 'none',
},
});

/**
* ## ARIA
*
* If the progress bar is describing the loading progress of a particular region of a page,
* you should use `aria-describedby` to point to the progress bar, and set the `aria-busy`
* attribute to `true` on that region until it has finished loading.
*/
const CircularProgress = React.forwardRef(function CircularProgress(
props,
ref,
) {
const {
classes,
className,
color,
disableShrink,
size,
style,
thickness,
value,
variant,
...other
} = props;

const circleStyle = {};
const rootStyle = {};
const rootProps = {};

if (variant === 'determinate' || variant === 'static') {
const circumference = 2 * Math.PI * ((SIZE - thickness) / 2);
circleStyle.strokeDasharray = circumference.toFixed(3);
rootProps['aria-valuenow'] = Math.round(value);

if (variant === 'static') {
circleStyle.strokeDashoffset = `${(
((100 - value) / 100) *
circumference
).toFixed(3)}px`;
rootStyle.transform = 'rotate(-90deg)';
} else {
circleStyle.strokeDashoffset = `${(
easeIn((100 - value) / 100) * circumference
).toFixed(3)}px`;
rootStyle.transform = `rotate(${(easeOut(value / 70) * 270).toFixed(
3,
)}deg)`;
}
}

return (
<div
className={clsx(
classes.root,
{
[classes[`color${capitalize(color)}`]]: color !== 'inherit',
[classes.indeterminate]: variant === 'indeterminate',
[classes.static]: variant === 'static',
},
className,
)}
style={{ width: size, height: size, ...rootStyle, ...style }}
ref={ref}
role="progressbar"
{...rootProps}
{...other}
>
<svg
className={classes.svg}
viewBox={`${SIZE / 2} ${SIZE / 2} ${SIZE} ${SIZE}`}
>
<circle
className={clsx(classes.circle, {
[classes.circleIndeterminate]: variant === 'indeterminate',
[classes.circleStatic]: variant === 'static',
[classes.circleDisableShrink]: disableShrink,
})}
style={circleStyle}
cx={SIZE}
cy={SIZE}
r={(SIZE - thickness) / 2}
fill="none"
strokeWidth={thickness}
/>
</svg>
</div>
);
});

CircularProgress.propTypes = {
/**
* Override or extend the styles applied to the component.
* See [CSS API](#css) below for more details.
*/
classes: PropTypes.object.isRequired,
/**
* @ignore
*/
className: PropTypes.string,
/**
* The color of the component. It supports those theme colors that make sense for this component.
*/
color: PropTypes.oneOf(['primary', 'secondary', 'inherit']),
/**
* If `true`, the shrink animation is disabled.
* This only works if variant is `indeterminate`.
*/
disableShrink: chainPropTypes(PropTypes.bool, props => {
if (props.disableShrink && props.variant !== 'indeterminate') {
return new Error(
'Material-UI: you have provided the `disableShrink` property ' +
'with a variant other than `indeterminate`. This will have no effect.',
);
}

return null;
}),
/**
* The size of the circle.
*/
size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* @ignore
*/
style: PropTypes.object,
/**
* The thickness of the circle.
*/
thickness: PropTypes.number,
/**
* The value of the progress indicator for the determinate and static variants.
* Value between 0 and 100.
*/
value: PropTypes.number,
/**
* The variant to use.
* Use indeterminate when there is no progress value.
*/
variant: PropTypes.oneOf(['determinate', 'indeterminate', 'static']),
};

CircularProgress.defaultProps = {
color: 'primary',
disableShrink: false,
size: 40,
thickness: 3.6,
value: 0,
variant: 'indeterminate',
};

export default withStyles(styles, { name: 'MuiCircularProgress', flip: false })(
CircularProgress,
);
61 changes: 61 additions & 0 deletions benchmark/index.js
@@ -0,0 +1,61 @@
/* eslint-disable */
const fs = require('fs');
const path = require('path');
const Table = require('cli-table');
const Benchmark = require('benchmark');
const { parse } = require('..');

console.log(`Node: ${process.version}`);

const head = ['fixture', 'timing'];

const files = ['./fixtures/CircularProgress.js'];

const table = new Table({
head,
style: {
head: ['bold'],
},
});

if (!global.gc) {
console.error(
'Garbage collection unavailable. Pass --expose-gc ' +
'when launching node to enable forced garbage collection.',
);
process.exit();
}

files.forEach(file => {
const code = fs.readFileSync(path.join(__dirname, file), 'utf-8');
const suite = new Benchmark.Suite(file.replace(/\.\/fixtures\//, ''));
const options = { filename: file, babelrc: false, configFile: false };

// warmup
parse(code, null, null, options);
global.gc();
suite.add(0, () => {
parse(code, null, null, options);
});
const result = [suite.name];
suite.on('cycle', function(event) {
{
// separate scope so we can cleanup all this afterwards
const bench = event.target;
const factor = bench.hz < 100 ? 100 : 1;
const msg = `${Math.round(bench.hz * factor) /
factor} ops/sec ±${Math.round(bench.stats.rme * 100) /
100}% (${Math.round(bench.stats.mean * 1000)}ms)`;
result.push(msg);
}
global.gc();
});

console.log(`Running benchmark for ${suite.name} ...`);
global.gc();
suite.run({ async: false });
global.gc(); // gc is disabled so ensure we run it
table.push(result);
});
global.gc(); // gc is disabled so ensure we run it
console.log(table.toString());
6 changes: 4 additions & 2 deletions bin/__tests__/example/customResolver.js
Expand Up @@ -16,8 +16,10 @@ const code = `
})
`;

module.exports = function(ast, recast) {
return new recast.types.NodePath(recast.parse(code)).get(
const { NodePath } = require('ast-types');

module.exports = function(ast, parser) {
return new NodePath(parser.parse(code)).get(
'program',
'body',
0,
Expand Down
6 changes: 1 addition & 5 deletions bin/__tests__/react-docgen-test.js
Expand Up @@ -6,15 +6,11 @@
*
*/

/*global jasmine, describe, it, expect, afterEach*/

// NOTE: This test spawns a subprocesses that load the files from dist/, not
// src/. Before running this test run `npm run build` or `npm run watch`.

const TEST_TIMEOUT = 120000;

jasmine.DEFAULT_TIMEOUT_INTERVAL = TEST_TIMEOUT;

const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
Expand Down Expand Up @@ -90,7 +86,7 @@ describe('react-docgen CLI', () => {
tempDir = null;
tempComponents = [];
tempNoComponents = [];
});
}, TEST_TIMEOUT);

it(
'reads from stdin',
Expand Down