Remove react-codemod

This commit is contained in:
Paul O’Shannessy 2015-10-19 14:31:31 -07:00
parent 244dd5da4c
commit caece8b4b7
28 changed files with 0 additions and 2295 deletions

View File

@ -11,9 +11,4 @@ docs/vendor/bundle/
examples/ examples/
# Ignore built files. # Ignore built files.
build/ build/
# react-codemod
packages/react-codemod/test/
packages/react-codemod/scripts/
packages/react-codemod/build/
packages/react-codemod/node_modules/
vendor/react-dom.js vendor/react-dom.js

View File

@ -1,111 +0,0 @@
## react-codemod
This repository contains a collection of codemod scripts based for use with
[JSCodeshift](https://github.com/facebook/jscodeshift) that help update React
APIs.
### Setup & Run
* `npm install -g jscodeshift`
* `git clone https://github.com/facebook/react.git` or download a zip file
from `https://github.com/facebook/react/archive/master.zip`
* `jscodeshift -t <codemod-script> <file>`
* Use the `-d` option for a dry-run and use `-p` to print the output
for comparison
### Included Scripts
`findDOMNode` updates `this.getDOMNode()` or `this.refs.foo.getDOMNode()`
calls inside of `React.createClass` components to `React.findDOMNode(foo)`. Note
that it will only look at code inside of `React.createClass` calls and only
update calls on the component instance or its refs. You can use this script to
update most calls to `getDOMNode` and then manually go through the remaining
calls.
* `jscodeshift -t react/packages/react-codemod/transforms/findDOMNode.js <file>`
`react-to-react-dom` updates code for the split of the `react` and `react-dom`
packages (e.g., `React.render` to `ReactDOM.render`). It looks for
`require('react')` and replaces the appropriate property accesses using
`require('react-dom')`. It does not support ES6 modules or other non-CommonJS
systems. We recommend performing the `findDOMNode` conversion first.
* `jscodeshift -t react/packages/react-codemod/transforms/react-to-react-dom.js <file>`
* After running the automated codemod, you may want to run a regex-based find-and-replace to remove extra whitespace between the added requires, such as `codemod.py -m -d src --extensions js '(var React\s*=\s*require\(.react.\);)\n\n(\s*var ReactDOM)' '\1\n\2'` using https://github.com/facebook/codemod.
`pure-render-mixin` removes `PureRenderMixin` and inlines
`shouldComponentUpdate` so that the ES2015 class transform can pick up the React
component and turn it into an ES2015 class. NOTE: This currently only works if you
are using the master version (>0.13.1) of React as it is using
`React.addons.shallowCompare`
* `jscodeshift -t react/packages/react-codemod/transforms/pure-render-mixin.js <file>`
* If `--mixin-name=<name>` is specified it will look for the specified name
instead of `PureRenderMixin`. Note that it is not possible to use a
namespaced name for the mixin. `mixins: [React.addons.PureRenderMixin]` will
not currently work.
`class` transforms `React.createClass` calls into ES2015 classes.
* `jscodeshift -t react/packages/react-codemod/transforms/class.js <file>`
* If `--no-super-class` is specified it will not extend
`React.Component` if `setState` and `forceUpdate` aren't being called in a
class. We do recommend always extending from `React.Component`, especially
if you are using or planning to use [Flow](http://flowtype.org/). Also make
sure you are not calling `setState` anywhere outside of your component.
These three scripts take an option `--no-explicit-require` if you don't have a
`require('React')` statement in your code files and if you access React as a
global.
### Explanation of the ES2015 class transform
* Ignore components with calls to deprecated APIs. This is very defensive, if
the script finds any identifiers called `isMounted`, `getDOMNode`,
`replaceProps`, `replaceState` or `setProps` it will skip the component.
* Replaces `var A = React.createClass(spec)` with
`class A (extends React.Component) {spec}`.
* Pulls out all statics defined on `statics` plus the few special cased
statics like `propTypes`, `childContextTypes`, `contextTypes` and
`displayName` and assigns them after the class is created.
`class A {}; A.foo = bar;`
* Takes `getDefaultProps` and inlines it as a static `defaultProps`.
If `getDefaultProps` is defined as a function with a single statement that
returns an object, it optimizes and transforms
`getDefaultProps() { return {foo: 'bar'}; }` into
`A.defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than
one statement it will transform into a self-invoking function like this:
`A.defaultProps = function() {…}();`. Note that this means that the function
will be executed only a single time per app-lifetime. In practice this
hasn't caused any issues `getDefaultProps` should not contain any
side-effects.
* Binds class methods to the instance if methods are referenced without being
called directly. It checks for `this.foo` but also traces variable
assignments like `var self = this; self.foo`. It does not bind functions
from the React API and ignores functions that are being called directly
(unless it is both called directly and passed around to somewhere else)
* Creates a constructor if necessary. This is necessary if either
`getInitialState` exists in the `React.createClass` spec OR if functions
need to be bound to the instance.
* When `--no-super-class` is passed it only optionally extends
`React.Component` when `setState` or `forceUpdate` are used within the
class.
The constructor logic is as follows:
* Call `super(props, context)` if the base class needs to be extended.
* Bind all functions that are passed around,
like `this.foo = this.foo.bind(this)`
* Inline `getInitialState` (and remove `getInitialState` from the spec). It
also updates access of `this.props.foo` to `props.foo` and adds `props` as
argument to the constructor. This is necessary in the case when the base
class does not need to be extended where `this.props` will only be set by
React after the constructor has been run.
* Changes `return StateObject` from `getInitialState` to assign `this.state`
directly.
### Recast Options
Options to [recast](https://github.com/benjamn/recast)'s printer can be provided
through the `printOptions` command line argument
* `jscodeshift -t transform.js <file> --printOptions='{"quote":"double"}'`

View File

@ -1,26 +0,0 @@
{
"name": "react-codemod",
"version": "3.0.0",
"description": "React codemod scripts",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react"
},
"scripts": {
"test": "jest"
},
"dependencies": {
"jscodeshift": "^0.3.7"
},
"devDependencies": {
"babel-jest": "^5.3.0",
"jest-cli": "^0.5.1"
},
"jest": {
"scriptPreprocessor": "./node_modules/babel-jest",
"testPathDirs": [
"test"
]
}
}

View File

@ -1,68 +0,0 @@
/**
* Copyright 2013-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 fs = require('fs');
var jscodeshift = require('jscodeshift');
function read(fileName) {
return fs.readFileSync(__dirname + '/../' + fileName, 'utf8');
}
function test(transformName, testFileName, options) {
var path = testFileName + '.js';
var source = read(testFileName + '.js');
var output = read(testFileName + '.output.js');
var transform = require('../../transforms/' + transformName);
expect(
(transform({path, source}, {jscodeshift}, options || {}) || '').trim()
).toEqual(
output.trim()
);
}
describe('Transform Tests', () => {
it('transforms the "findDOMNode" tests correctly', () => {
test('findDOMNode', 'findDOMNode-test');
});
it('transforms the "pure-render-mixin" tests correctly', () => {
test('pure-render-mixin', 'pure-render-mixin-test');
test('pure-render-mixin', 'pure-render-mixin-test2');
test('pure-render-mixin', 'pure-render-mixin-test3');
test('pure-render-mixin', 'pure-render-mixin-test4', {
'mixin-name': 'ReactComponentWithPureRenderMixin',
});
});
it('transforms the "class" tests correctly', () => {
test('class', 'class-test');
test('class', 'class-test2', {
'super-class': false
});
test('class', 'class-test3');
});
it('transforms exports class', () => {
test('class', 'export-default-class-test');
});
});

View File

@ -1,133 +0,0 @@
'use strict';
var React = require('React');
var Relay = require('Relay');
var Image = require('Image.react');
/*
* Multiline
*/
var MyComponent = React.createClass({
getInitialState: function() {
var x = this.props.foo;
return {
heyoo: 23,
};
},
foo: function() {
this.setState({heyoo: 24});
}
});
// Class comment
var MyComponent2 = React.createClass({
getDefaultProps: function() {
return {a: 1};
},
foo: function() {
pass(this.foo);
this.forceUpdate();
}
});
var MyComponent3 = React.createClass({
statics: {
someThing: 10,
foo: function() {},
},
propTypes: {
highlightEntities: React.PropTypes.bool,
linkifyEntities: React.PropTypes.bool,
text: React.PropTypes.shape({
text: React.PropTypes.string,
ranges: React.PropTypes.array
}).isRequired
},
getDefaultProps: function() {
foo();
return {
linkifyEntities: true,
highlightEntities: false
};
},
getInitialState: function() {
this.props.foo();
return {
heyoo: 23,
};
},
_renderText: function(text) {
return <Text text={text} />;
},
_renderImageRange: function(text, range) {
var image = range.image;
if (image) {
return (
<Image
src={image.uri}
height={image.height / image.scale}
width={image.width / image.scale}
/>
);
}
},
autobindMe: function() {},
dontAutobindMe: function() {},
// Function comment
_renderRange: function(text, range) {
var self = this;
self.dontAutobindMe();
call(self.autobindMe);
var type = rage.type;
var {highlightEntities} = this.props;
if (type === 'ImageAtRange') {
return this._renderImageRange(text, range);
}
if (this.props.linkifyEntities) {
text =
<Link href={usersURI}>
{text}
</Link>;
} else {
text = <span>{text}</span>;
}
return text;
},
/* This is a comment */
render: function() {
var content = this.props.text;
return (
<BaseText
{...this.props}
textRenderer={this._renderText}
rangeRenderer={this._renderRange}
text={content.text}
/>
);
}
});
var MyComponent4 = React.createClass({
foo: callMeMaybe(),
render: function() {},
});
module.exports = Relay.createContainer(MyComponent, {
queries: {
me: Relay.graphql`this is not graphql`,
}
});

View File

@ -1,144 +0,0 @@
'use strict';
var React = require('React');
var Relay = require('Relay');
var Image = require('Image.react');
/*
* Multiline
*/
class MyComponent extends React.Component {
constructor(props, context) {
super(props, context);
var x = props.foo;
this.state = {
heyoo: 23,
};
}
foo() {
this.setState({heyoo: 24});
}
}
// Class comment
class MyComponent2 extends React.Component {
constructor(props, context) {
super(props, context);
this.foo = this.foo.bind(this);
}
foo() {
pass(this.foo);
this.forceUpdate();
}
}
MyComponent2.defaultProps = {a: 1};
class MyComponent3 extends React.Component {
constructor(props, context) {
super(props, context);
this._renderRange = this._renderRange.bind(this);
this._renderText = this._renderText.bind(this);
this.autobindMe = this.autobindMe.bind(this);
props.foo();
this.state = {
heyoo: 23,
};
}
_renderText(text) {
return <Text text={text} />;
}
_renderImageRange(text, range) {
var image = range.image;
if (image) {
return (
<Image
src={image.uri}
height={image.height / image.scale}
width={image.width / image.scale}
/>
);
}
}
autobindMe() {}
dontAutobindMe() {}
// Function comment
_renderRange(text, range) {
var self = this;
self.dontAutobindMe();
call(self.autobindMe);
var type = rage.type;
var {highlightEntities} = this.props;
if (type === 'ImageAtRange') {
return this._renderImageRange(text, range);
}
if (this.props.linkifyEntities) {
text =
<Link href={usersURI}>
{text}
</Link>;
} else {
text = <span>{text}</span>;
}
return text;
}
/* This is a comment */
render() {
var content = this.props.text;
return (
<BaseText
{...this.props}
textRenderer={this._renderText}
rangeRenderer={this._renderRange}
text={content.text}
/>
);
}
}
MyComponent3.defaultProps = function() {
foo();
return {
linkifyEntities: true,
highlightEntities: false
};
}();
MyComponent3.foo = function() {};
MyComponent3.propTypes = {
highlightEntities: React.PropTypes.bool,
linkifyEntities: React.PropTypes.bool,
text: React.PropTypes.shape({
text: React.PropTypes.string,
ranges: React.PropTypes.array
}).isRequired
};
MyComponent3.someThing = 10;
var MyComponent4 = React.createClass({
foo: callMeMaybe(),
render: function() {},
});
module.exports = Relay.createContainer(MyComponent, {
queries: {
me: Relay.graphql`this is not graphql`,
}
});

View File

@ -1,33 +0,0 @@
'use strict';
var React = require('React');
var IdontNeedAParent = React.createClass({
render: function() {
return <div />;
}
});
var ButIDo = React.createClass({
foo: function() {
this.setState({banana: '?'});
},
render: function() {
return <div />;
}
});
var IAccessProps = React.createClass({
getInitialState: function() {
return {
relayReleaseDate: this.props.soon,
};
},
render: function() {
return
}
});

View File

@ -1,31 +0,0 @@
'use strict';
var React = require('React');
class IdontNeedAParent {
render() {
return <div />;
}
}
class ButIDo extends React.Component {
foo() {
this.setState({banana: '?'});
}
render() {
return <div />;
}
}
class IAccessProps {
constructor(props) {
this.state = {
relayReleaseDate: props.soon,
};
}
render() {
return
}
}

View File

@ -1,20 +0,0 @@
'use strict';
var React = require('React');
// Comment
module.exports = React.createClass({
propTypes: {
foo: React.PropTypes.bool,
},
getInitialState: function() {
return {
foo: 'bar',
};
},
render: function() {
return <div />;
}
});

View File

@ -1,22 +0,0 @@
'use strict';
var React = require('React');
// Comment
module.exports = class extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
foo: 'bar',
};
}
render() {
return <div />;
}
};
module.exports.propTypes = {
foo: React.PropTypes.bool,
};

View File

@ -1,19 +0,0 @@
'use strict';
import React from 'React';
export default React.createClass({
getInitialState: function() {
return {
foo: 'bar',
};
},
propTypes: {
foo: React.PropTypes.string,
},
render: function() {
return <div />;
}
});

View File

@ -1,21 +0,0 @@
'use strict';
import React from 'React';
export default class extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
foo: 'bar',
};
}
static propTypes = {
foo: React.PropTypes.string,
};
render() {
return <div />;
}
}

View File

@ -1,34 +0,0 @@
'use strict';
var React = require('React');
var Composer = React.createClass({
componentWillReceiveProps: function(nextProps) {
this.getDOMNode();
return foo(this.refs.input.getDOMNode());
},
foo: function() {
var ref = 'foo';
var element = this.refs[ref];
var domNode = element.getDOMNode();
},
bar: function() {
var thing = this.refs.foo;
thing.getDOMNode();
},
foobar: function() {
passThisOn(this.refs.main.refs.list.getDOMNode());
}
});
var SomeDialog = React.createClass({
render: function() {
call(this.refs.SomeThing);
return (
<div />
);
}
});

View File

@ -1,34 +0,0 @@
'use strict';
var React = require('React');
var Composer = React.createClass({
componentWillReceiveProps: function(nextProps) {
React.findDOMNode(this);
return foo(React.findDOMNode(this.refs.input));
},
foo: function() {
var ref = 'foo';
var element = this.refs[ref];
var domNode = React.findDOMNode(element);
},
bar: function() {
var thing = this.refs.foo;
React.findDOMNode(thing);
},
foobar: function() {
passThisOn(React.findDOMNode(this.refs.main.refs.list));
}
});
var SomeDialog = React.createClass({
render: function() {
call(this.refs.SomeThing);
return (
<div />
);
}
});

View File

@ -1,45 +0,0 @@
var React = require('react/addons');
var PureRenderMixin = React.addons.PureRenderMixin;
var MyComponent = React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div />;
}
});
var MyMixedComponent = React.createClass({
mixins: [PureRenderMixin, SomeOtherMixin],
render: function() {
return <div />;
}
});
var MyFooComponent = React.createClass({
mixins: [PureRenderMixin, SomeOtherMixin],
render: function() {
return <div />;
},
foo: function() {
}
});
var MyStupidComponent = React.createClass({
mixins: [PureRenderMixin],
shouldComponentUpdate: function() {
return !!'wtf is this doing here?';
},
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,55 +0,0 @@
var React = require('react/addons');
var PureRenderMixin = React.addons.PureRenderMixin;
var MyComponent = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
},
render: function() {
return <div />;
}
});
var MyMixedComponent = React.createClass({
mixins: [SomeOtherMixin],
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
},
render: function() {
return <div />;
}
});
var MyFooComponent = React.createClass({
mixins: [SomeOtherMixin],
render: function() {
return <div />;
},
foo: function() {
},
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
}
});
var MyStupidComponent = React.createClass({
mixins: [PureRenderMixin],
shouldComponentUpdate: function() {
return !!'wtf is this doing here?';
},
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,13 +0,0 @@
var React = require('react/addons');
var PureRenderMixin = React.addons.PureRenderMixin;
var MyComponent = React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,13 +0,0 @@
var React = require('react/addons');
var MyComponent = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
},
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,14 +0,0 @@
var React = require('react/addons');
var Foo = 'Foo';
var PureRenderMixin = React.addons.PureRenderMixin;
var MyComponent = React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,15 +0,0 @@
var React = require('react/addons');
var Foo = 'Foo';
var MyComponent = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
},
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,12 +0,0 @@
var React = require('React');
var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
var MyComponent = React.createClass({
mixins: [ReactComponentWithPureRenderMixin],
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,13 +0,0 @@
var React = require('React');
var MyComponent = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return React.addons.shallowCompare(this, nextProps, nextState);
},
render: function() {
return <div />;
}
});
module.exports = MyComponent;

View File

@ -1,555 +0,0 @@
/**
* Copyright 2013-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';
module.exports = (file, api, options) => {
const j = api.jscodeshift;
require('./utils/array-polyfills');
const ReactUtils = require('./utils/ReactUtils')(j);
const printOptions =
options.printOptions || {quote: 'single', trailingComma: true};
const root = j(file.source);
const AUTOBIND_IGNORE_KEYS = {
componentDidMount: true,
componentDidUpdate: true,
componentWillReceiveProps: true,
componentWillMount: true,
componentWillUpdate: true,
componentWillUnmount: true,
getDefaultProps: true,
getInitialState: true,
render: true,
shouldComponentUpdate: true,
};
const BASE_COMPONENT_METHODS = ['setState', 'forceUpdate'];
const DEFAULT_PROPS_FIELD = 'getDefaultProps';
const DEFAULT_PROPS_KEY = 'defaultProps';
const GET_INITIAL_STATE_FIELD = 'getInitialState';
const DEPRECATED_APIS = [
'getDOMNode',
'isMounted',
'replaceProps',
'replaceState',
'setProps',
];
const STATIC_KEY = 'statics';
const STATIC_KEYS = {
childContextTypes: true,
contextTypes: true,
displayName: true,
propTypes: true,
};
// ---------------------------------------------------------------------------
// Checks if the module uses mixins or accesses deprecated APIs.
const checkDeprecatedAPICalls = classPath =>
DEPRECATED_APIS.reduce(
(acc, name) =>
acc + j(classPath)
.find(j.Identifier, {name})
.size(),
0
) > 0;
const callsDeprecatedAPIs = classPath => {
if (checkDeprecatedAPICalls(classPath)) {
console.log(
file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' +
'was skipped because of deprecated API calls. Remove calls to ' +
DEPRECATED_APIS.join(', ') + ' in your React component and re-run ' +
'this script.'
);
return false;
}
return true;
};
const canConvertToClass = classPath => {
const specPath = ReactUtils.getReactCreateClassSpec(classPath);
const invalidProperties = specPath.properties.filter(prop => (
!prop.key.name || (
!STATIC_KEYS[prop.key.name] &&
STATIC_KEY != prop.key.name &&
!filterDefaultPropsField(prop) &&
!filterGetInitialStateField(prop) &&
!isFunctionExpression(prop)
)
));
if (invalidProperties.length) {
const invalidText = invalidProperties
.map(prop => prop.key.name ? prop.key.name : prop.key)
.join(', ');
console.log(
file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' +
'was skipped because of invalid field(s) `' + invalidText + '` on ' +
'the React component. Remove any right-hand-side expressions that ' +
'are not simple, like: `componentWillUpdate: createWillUpdate()` or ' +
'`render: foo ? renderA : renderB`.'
);
}
return !invalidProperties.length;
};
const hasMixins = classPath => {
if (ReactUtils.hasMixins(classPath)) {
console.log(
file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' +
'was skipped because of mixins.'
);
return false;
}
return true;
};
// ---------------------------------------------------------------------------
// Helpers
const createFindPropFn = prop => property => (
property.key &&
property.key.type === 'Identifier' &&
property.key.name === prop
);
const filterDefaultPropsField = node =>
createFindPropFn(DEFAULT_PROPS_FIELD)(node);
const filterGetInitialStateField = node =>
createFindPropFn(GET_INITIAL_STATE_FIELD)(node);
const findGetDefaultProps = specPath =>
specPath.properties.find(createFindPropFn(DEFAULT_PROPS_FIELD));
const findGetInitialState = specPath =>
specPath.properties.find(createFindPropFn(GET_INITIAL_STATE_FIELD));
// This is conservative; only check for `setState` and `forceUpdate` literals
// instead of also checking which objects they are called on.
const shouldExtendReactComponent = classPath =>
BASE_COMPONENT_METHODS.some(name => (
j(classPath)
.find(j.Identifier, {name})
.size() > 0
));
const withComments = (to, from) => {
to.comments = from.comments;
return to;
};
// ---------------------------------------------------------------------------
// Collectors
const isFunctionExpression = node => (
node.key &&
node.key.type === 'Identifier' &&
node.value &&
node.value.type === 'FunctionExpression'
);
const collectStatics = specPath => {
const statics = specPath.properties.find(createFindPropFn('statics'));
const staticsObject =
(statics && statics.value && statics.value.properties) || [];
const getDefaultProps = findGetDefaultProps(specPath);
if (getDefaultProps) {
staticsObject.push(createDefaultProps(getDefaultProps));
}
return (
staticsObject.concat(specPath.properties.filter(property =>
property.key && STATIC_KEYS[property.key.name]
))
.sort((a, b) => a.key.name < b.key.name)
);
};
const collectFunctions = specPath => specPath.properties
.filter(prop =>
!(filterDefaultPropsField(prop) || filterGetInitialStateField(prop))
)
.filter(isFunctionExpression);
const findAutobindNamesFor = (subtree, fnNames, literalOrIdentifier) => {
const node = literalOrIdentifier;
const autobindNames = {};
j(subtree)
.find(j.MemberExpression, {
object: node.name ? {
type: node.type,
name: node.name,
} : {type: node.type},
property: {
type: 'Identifier',
},
})
.filter(path => path.value.property && fnNames[path.value.property.name])
.filter(path => {
const call = path.parent.value;
return !(
call &&
call.type === 'CallExpression' &&
call.callee.type === 'MemberExpression' &&
call.callee.object.type === node.type &&
call.callee.object.name === node.name &&
call.callee.property.type === 'Identifier' &&
call.callee.property.name === path.value.property.name
);
})
.forEach(path => autobindNames[path.value.property.name] = true);
return Object.keys(autobindNames);
};
const collectAutoBindFunctions = (functions, classPath) => {
const fnNames = {};
functions
.filter(fn => !AUTOBIND_IGNORE_KEYS[fn.key.name])
.forEach(fn => fnNames[fn.key.name] = true);
const autobindNames = {};
const add = name => autobindNames[name] = true;
// Find `this.<foo>`
findAutobindNamesFor(classPath, fnNames, j.thisExpression()).forEach(add);
// Find `self.<foo>` if `self = this`
j(classPath)
.findVariableDeclarators()
.filter(path => (
path.value.id.type === 'Identifier' &&
path.value.init &&
path.value.init.type === 'ThisExpression'
))
.forEach(path =>
findAutobindNamesFor(
j(path).closest(j.FunctionExpression).get(),
fnNames,
path.value.id
).forEach(add)
);
return Object.keys(autobindNames).sort();
};
// ---------------------------------------------------------------------------
// Boom!
const createMethodDefinition = fn =>
withComments(j.methodDefinition(
'method',
fn.key,
fn.value
), fn);
const createBindAssignment = name =>
j.expressionStatement(
j.assignmentExpression(
'=',
j.memberExpression(
j.thisExpression(),
j.identifier(name),
false
),
j.callExpression(
j.memberExpression(
j.memberExpression(
j.thisExpression(),
j.identifier(name),
false
),
j.identifier('bind'),
false
),
[j.thisExpression()]
)
)
);
const createSuperCall = shouldAddSuperCall =>
!shouldAddSuperCall ?
[] :
[
j.expressionStatement(
j.callExpression(
j.identifier('super'),
[j.identifier('props'), j.identifier('context')]
)
),
];
const updatePropsAccess = getInitialState =>
getInitialState ?
j(getInitialState)
.find(j.MemberExpression, {
object: {
type: 'ThisExpression',
},
property: {
type: 'Identifier',
name: 'props',
},
})
.forEach(path => j(path).replaceWith(j.identifier('props')))
.size() > 0 :
false;
const inlineGetInitialState = getInitialState => {
if (!getInitialState) {
return [];
}
return getInitialState.value.body.body.map(statement => {
if (statement.type === 'ReturnStatement') {
return j.expressionStatement(
j.assignmentExpression(
'=',
j.memberExpression(
j.thisExpression(),
j.identifier('state'),
false
),
statement.argument
)
);
}
return statement;
});
};
const createConstructorArgs = (shouldAddSuperClass, hasPropsAccess) => {
if (shouldAddSuperClass) {
return [j.identifier('props'), j.identifier('context')];
} else if (hasPropsAccess) {
return [j.identifier('props')];
} else {
return [];
}
};
const createConstructor = (
getInitialState,
autobindFunctions,
shouldAddSuperClass
) => {
if (!getInitialState && !autobindFunctions.length) {
return [];
}
const hasPropsAccess = updatePropsAccess(getInitialState);
return [
createMethodDefinition({
key: j.identifier('constructor'),
value: j.functionExpression(
null,
createConstructorArgs(shouldAddSuperClass, hasPropsAccess),
j.blockStatement(
[].concat(
createSuperCall(shouldAddSuperClass),
autobindFunctions.map(createBindAssignment),
inlineGetInitialState(getInitialState)
)
)
),
}),
];
};
const createESClass = (
name,
properties,
getInitialState,
autobindFunctions,
comments,
shouldAddSuperClass
) =>
withComments(j.classDeclaration(
name ? j.identifier(name) : null,
j.classBody(
[].concat(
createConstructor(
getInitialState,
autobindFunctions,
shouldAddSuperClass
),
properties
)
),
shouldAddSuperClass ? j.memberExpression(
j.identifier('React'),
j.identifier('Component'),
false
) : null
), {comments});
const createStaticAssignment = (name, staticProperty) =>
withComments(j.expressionStatement(
j.assignmentExpression(
'=',
j.memberExpression(
name,
j.identifier(staticProperty.key.name),
false
),
staticProperty.value
)
), staticProperty);
const createStaticAssignmentExpressions = (name, statics) =>
statics.map(staticProperty => createStaticAssignment(name, staticProperty));
const createStaticClassProperty = staticProperty =>
withComments(j.classProperty(
j.identifier(staticProperty.key.name),
staticProperty.value,
null,
true
), staticProperty);
const createStaticClassProperties = statics =>
statics.map(createStaticClassProperty);
const hasSingleReturnStatement = value => (
value.type === 'FunctionExpression' &&
value.body &&
value.body.type === 'BlockStatement' &&
value.body.body &&
value.body.body.length === 1 &&
value.body.body[0].type === 'ReturnStatement' &&
value.body.body[0].argument &&
value.body.body[0].argument.type === 'ObjectExpression'
);
const createDefaultPropsValue = value => {
if (hasSingleReturnStatement(value)) {
return value.body.body[0].argument;
} else {
return j.callExpression(
value,
[]
);
}
};
const createDefaultProps = prop =>
withComments(
j.property(
'init',
j.identifier(DEFAULT_PROPS_KEY),
createDefaultPropsValue(prop.value)
),
prop
);
const getComments = classPath => {
if (classPath.value.comments) {
return classPath.value.comments;
}
const declaration = j(classPath).closest(j.VariableDeclaration);
if (declaration.size()) {
return declaration.get().value.comments;
}
return null;
};
const createModuleExportsMemberExpression = () =>
j.memberExpression(
j.identifier('module'),
j.identifier('exports'),
false
);
const updateToClass = (classPath, shouldExtend, type) => {
const specPath = ReactUtils.getReactCreateClassSpec(classPath);
const name = ReactUtils.getComponentName(classPath);
const statics = collectStatics(specPath);
const functions = collectFunctions(specPath);
const comments = getComments(classPath);
const autobindFunctions = collectAutoBindFunctions(functions, classPath);
const getInitialState = findGetInitialState(specPath);
const staticName =
name ? j.identifier(name) : createModuleExportsMemberExpression();
var path;
if (type == 'moduleExports' || type == 'exportDefault') {
path = ReactUtils.findReactCreateClassCallExpression(classPath);
} else {
path = j(classPath).closest(j.VariableDeclaration);
}
const properties =
(type == 'exportDefault') ? createStaticClassProperties(statics) : [];
path.replaceWith(
createESClass(
name,
properties.concat(functions.map(createMethodDefinition)),
getInitialState,
autobindFunctions,
comments,
shouldExtend || shouldExtendReactComponent(classPath)
)
);
if (type == 'moduleExports' || type == 'var') {
const staticAssignments = createStaticAssignmentExpressions(
staticName,
statics
);
if (type == 'moduleExports') {
root.get().value.program.body.push(...staticAssignments);
} else {
path.insertAfter(staticAssignments.reverse());
}
}
};
if (
!options['explicit-require'] || ReactUtils.hasReact(root)
) {
const apply = (path, type) =>
path
.filter(hasMixins)
.filter(callsDeprecatedAPIs)
.filter(canConvertToClass)
.forEach(classPath => updateToClass(
classPath,
!options['super-class'],
type
));
const didTransform = (
apply(ReactUtils.findReactCreateClass(root), 'var')
.size() +
apply(ReactUtils.findReactCreateClassModuleExports(root), 'moduleExports')
.size() +
apply(ReactUtils.findReactCreateClassExportDefault(root), 'exportDefault')
.size()
) > 0;
if (didTransform) {
return root.toSource(printOptions);
}
}
return null;
};

View File

@ -1,144 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
function getDOMNodeToFindDOMNode(file, api, options) {
const j = api.jscodeshift;
require('./utils/array-polyfills');
const ReactUtils = require('./utils/ReactUtils')(j);
const printOptions =
options.printOptions || {quote: 'single', trailingComma: true};
const root = j(file.source);
const createReactFindDOMNodeCall = arg => j.callExpression(
j.memberExpression(
j.identifier('React'),
j.identifier('findDOMNode'),
false
),
[arg]
);
const updateRefCall = (path, refName) => {
j(path)
.find(j.CallExpression, {
callee: {
object: {
type: 'Identifier',
name: refName,
},
property: {
type: 'Identifier',
name: 'getDOMNode',
},
},
})
.forEach(callPath => j(callPath).replaceWith(
createReactFindDOMNodeCall(j.identifier(refName))
));
};
const updateToFindDOMNode = classPath => {
var sum = 0;
// this.getDOMNode()
sum += j(classPath)
.find(j.CallExpression, {
callee: {
object: {
type: 'ThisExpression',
},
property: {
type: 'Identifier',
name: 'getDOMNode',
},
},
})
.forEach(path => j(path).replaceWith(
createReactFindDOMNodeCall(j.thisExpression())
))
.size();
// this.refs.xxx.getDOMNode() or this.refs.xxx.refs.yyy.getDOMNode()
sum += j(classPath)
.find(j.MemberExpression, {
object: {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
object: {
type: 'ThisExpression',
},
property: {
type: 'Identifier',
name: 'refs',
},
},
},
})
.closest(j.CallExpression)
.filter(path => (
path.value.callee.property &&
path.value.callee.property.type === 'Identifier' &&
path.value.callee.property.name === 'getDOMNode'
))
.forEach(path => j(path).replaceWith(
createReactFindDOMNodeCall(path.value.callee.object)
))
.size();
// someVariable.getDOMNode() wherre `someVariable = this.refs.xxx`
sum += j(classPath)
.findVariableDeclarators()
.filter(path => {
const init = path.value.init;
const value = init && init.object;
return (
value &&
value.type === 'MemberExpression' &&
value.object &&
value.object.type === 'ThisExpression' &&
value.property &&
value.property.type === 'Identifier' &&
value.property.name === 'refs' &&
init.property &&
init.property.type === 'Identifier'
);
})
.forEach(path => j(path)
.closest(j.FunctionExpression)
.forEach(fnPath => updateRefCall(fnPath, path.value.id.name))
)
.size();
return sum > 0;
};
if (
!options['explicit-require'] ||
ReactUtils.hasReact(root)
) {
const didTransform = ReactUtils
.findReactCreateClass(root)
.filter(updateToFindDOMNode)
.size() > 0;
if (didTransform) {
return root.toSource(printOptions);
}
}
return null;
}
module.exports = getDOMNodeToFindDOMNode;

View File

@ -1,188 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
function removePureRenderMixin(file, api, options) {
const j = api.jscodeshift;
require('./utils/array-polyfills');
const ReactUtils = require('./utils/ReactUtils')(j);
const printOptions =
options.printOptions || {quote: 'single', trailingComma: true};
const root = j(file.source);
const PURE_RENDER_MIXIN = options['mixin-name'] || 'PureRenderMixin';
const SHOULD_COMPONENT_UPDATE = 'shouldComponentUpdate';
const NEXT_PROPS = 'nextProps';
const NEXT_STATE = 'nextState';
// ---------------------------------------------------------------------------
// shouldComponentUpdate
const createShouldComponentUpdateFunction = () =>
j.functionExpression(
null,
[j.identifier(NEXT_PROPS), j.identifier(NEXT_STATE)],
j.blockStatement([
j.returnStatement(
j.callExpression(
j.memberExpression(
j.identifier('React'),
j.memberExpression(
j.identifier('addons'),
j.identifier('shallowCompare'),
false
),
false
),
[
j.thisExpression(),
j.identifier(NEXT_PROPS),
j.identifier(NEXT_STATE),
]
)
),
])
);
const createShouldComponentUpdateProperty = () =>
j.property(
'init',
j.identifier(SHOULD_COMPONENT_UPDATE),
createShouldComponentUpdateFunction()
);
const hasShouldComponentUpdate = classPath =>
ReactUtils.getReactCreateClassSpec(classPath)
.properties.every(property =>
property.key.name !== SHOULD_COMPONENT_UPDATE
);
// ---------------------------------------------------------------------------
// Mixin related code
const isPureRenderMixin = node => (
node.type === 'Identifier' &&
node.name === PURE_RENDER_MIXIN
);
const hasPureRenderMixin = classPath => {
const spec = ReactUtils.getReactCreateClassSpec(classPath);
const mixin = spec && spec.properties.find(ReactUtils.isMixinProperty);
return mixin && mixin.value.elements.some(isPureRenderMixin);
};
const removeMixin = elements =>
j.property(
'init',
j.identifier('mixins'),
j.arrayExpression(
elements.filter(element => !isPureRenderMixin(element))
)
);
// ---------------------------------------------------------------------------
// Boom!
const insertShouldComponentUpdate = properties => {
const length = properties.length;
const lastProp = properties[length - 1];
// I wouldn't dare insert at the bottom if the last function is render
if (
lastProp.key.type === 'Identifier' &&
lastProp.key.name === 'render'
) {
properties.splice(
length - 1,
1,
createShouldComponentUpdateProperty(),
lastProp
);
} else {
properties.push(createShouldComponentUpdateProperty());
}
return properties;
};
const cleanupReactComponent = classPath => {
const spec = ReactUtils.getReactCreateClassSpec(classPath);
const properties = spec.properties
.map(property => {
if (ReactUtils.isMixinProperty(property)) {
const elements = property.value.elements;
return (elements.length !== 1) ? removeMixin(elements) : null;
}
return property;
})
.filter(property => !!property);
ReactUtils.findReactCreateClassCallExpression(classPath).replaceWith(
ReactUtils.createCreateReactClassCallExpression(
insertShouldComponentUpdate(properties)
)
);
};
// Remove it if only two or fewer are left:
// var PureRenderMixin = React.addons.PureRenderMixin;
const hasPureRenderIdentifiers = path =>
path.find(j.Identifier, {
name: PURE_RENDER_MIXIN,
}).size() > 2;
const deletePureRenderMixin = path => {
if (hasPureRenderIdentifiers(path)) {
return;
}
const declaration = path
.findVariableDeclarators(PURE_RENDER_MIXIN)
.closest(j.VariableDeclaration);
if (declaration.size > 1) {
declaration.forEach(p =>
j(p).replaceWith(
j.variableDeclaration(
'var',
p.value.declarations.filter(isPureRenderMixin)
)
)
);
} else {
// Let's assume the variable declaration happens at the top level
const program = declaration.closest(j.Program).get();
const body = program.value.body;
const index = body.indexOf(declaration.get().value);
if (index !== -1) {
body.splice(index, 1);
}
}
};
if (
!options['explicit-require'] ||
ReactUtils.hasReact(root)
) {
const didTransform = ReactUtils
.findReactCreateClass(root)
.filter(hasPureRenderMixin)
.filter(hasShouldComponentUpdate)
.forEach(cleanupReactComponent)
.size() > 0;
if (didTransform) {
deletePureRenderMixin(root);
return root.toSource(printOptions);
}
}
return null;
}
module.exports = removePureRenderMixin;

View File

@ -1,321 +0,0 @@
/**
* Copyright 2013-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';
var CORE_PROPERTIES = [
'Children',
'Component',
'createElement',
'cloneElement',
'isValidElement',
'PropTypes',
'createClass',
'createFactory',
'createMixin',
'DOM',
'__spread',
];
var DOM_PROPERTIES = [
'findDOMNode',
'render',
'unmountComponentAtNode',
'unstable_batchedUpdates',
'unstable_renderSubtreeIntoContainer',
];
var DOM_SERVER_PROPERTIES = [
'renderToString',
'renderToStaticMarkup',
];
function reportError(node, error) {
throw new Error(
`At ${node.loc.start.line}:${node.loc.start.column}: ${error}`
);
}
function isRequire(path, moduleName) {
return (
path.value.type === 'CallExpression' &&
path.value.callee.type === 'Identifier' &&
path.value.callee.name === 'require' &&
path.value.arguments.length === 1 &&
path.value.arguments[0].type === 'Literal' &&
path.value.arguments[0].value === moduleName
);
}
module.exports = function(file, api) {
var j = api.jscodeshift;
var root = j(file.source);
[
['React', 'ReactDOM', 'ReactDOMServer'],
['react', 'react-dom', 'react-dom/server'],
].forEach(function(pair) {
var coreModuleName = pair[0];
var domModuleName = pair[1];
var domServerModuleName = pair[2];
var domAlreadyDeclared = false;
var domServerAlreadyDeclared = false;
var coreRequireDeclarator;
root
.find(j.CallExpression)
.filter(p => isRequire(p, coreModuleName))
.forEach(p => {
var name, scope;
if (p.parent.value.type === 'VariableDeclarator') {
if (p.parent.value.id.type === 'ObjectPattern') {
var pattern = p.parent.value.id;
var all = pattern.properties.every(function(prop) {
if (prop.key.type === 'Identifier') {
name = prop.key.name;
return CORE_PROPERTIES.indexOf(name) !== -1;
}
return false;
});
if (all) {
// var {PropTypes} = require('React'); so leave alone
return;
}
}
if (coreRequireDeclarator) {
reportError(
p.value,
'Multiple declarations of React'
);
}
if (p.parent.value.id.type !== 'Identifier') {
reportError(
p.value,
'Unexpected destructuring in require of ' + coreModuleName
);
}
name = p.parent.value.id.name;
scope = p.scope.lookup(name);
if (scope.declares('ReactDOM')) {
console.log('Using existing ReactDOM var in ' + file.path);
domAlreadyDeclared = true;
}
if (scope.declares('ReactDOMServer')) {
console.log('Using existing ReactDOMServer var in ' + file.path);
domServerAlreadyDeclared = true;
}
coreRequireDeclarator = p.parent;
} else if (p.parent.value.type === 'AssignmentExpression') {
if (p.parent.value.left.type !== 'Identifier') {
reportError(
p.value,
'Unexpected destructuring in require of ' + coreModuleName
);
}
name = p.parent.value.left.name;
scope = p.scope.lookup(name);
var reactBindings = scope.getBindings()[name];
if (reactBindings.length !== 1) {
throw new Error(
'Unexpected number of bindings: ' + reactBindings.length
);
}
coreRequireDeclarator = reactBindings[0].parent;
if (coreRequireDeclarator.value.init &&
!isRequire(coreRequireDeclarator.get('init'), coreModuleName)) {
reportError(
coreRequireDeclarator.value,
'Unexpected initialization of ' + coreModuleName
);
}
if (scope.declares('ReactDOM')) {
console.log('Using existing ReactDOM var in ' + file.path);
domAlreadyDeclared = true;
}
if (scope.declares('ReactDOMServer')) {
console.log('Using existing ReactDOMServer var in ' + file.path);
domServerAlreadyDeclared = true;
}
}
});
if (!coreRequireDeclarator) {
return;
}
if (!domAlreadyDeclared &&
root.find(j.Identifier, {name: 'ReactDOM'}).size() > 0) {
throw new Error(
'ReactDOM is already defined in a different scope than React'
);
}
if (!domServerAlreadyDeclared &&
root.find(j.Identifier, {name: 'ReactDOMServer'}).size() > 0) {
throw new Error(
'ReactDOMServer is already defined in a different scope than React'
);
}
var coreName = coreRequireDeclarator.value.id.name;
var processed = new Set();
var requireAssignments = [];
var coreUses = 0;
var domUses = 0;
var domServerUses = 0;
root
.find(j.Identifier, {name: coreName})
.forEach(p => {
if (processed.has(p.value)) {
// https://github.com/facebook/jscodeshift/issues/36
return;
}
processed.add(p.value);
if (p.parent.value.type === 'MemberExpression' ||
p.parent.value.type === 'QualifiedTypeIdentifier') {
var left;
var right;
if (p.parent.value.type === 'MemberExpression') {
left = p.parent.value.object;
right = p.parent.value.property;
} else {
left = p.parent.value.qualification;
right = p.parent.value.id;
}
if (left === p.value) {
// React.foo (or React[foo])
if (right.type === 'Identifier') {
var name = right.name;
if (CORE_PROPERTIES.indexOf(name) !== -1) {
coreUses++;
} else if (DOM_PROPERTIES.indexOf(name) !== -1) {
domUses++;
j(p).replaceWith(j.identifier('ReactDOM'));
} else if (DOM_SERVER_PROPERTIES.indexOf(name) !== -1) {
domServerUses++;
j(p).replaceWith(j.identifier('ReactDOMServer'));
} else {
throw new Error('Unknown property React.' + name);
}
}
} else if (right === p.value) {
// foo.React, no need to transform
} else {
throw new Error('unimplemented');
}
} else if (p.parent.value.type === 'VariableDeclarator') {
if (p.parent.value.id === p.value) {
// var React = ...;
} else if (p.parent.value.init === p.value) {
// var ... = React;
var pattern = p.parent.value.id;
if (pattern.type === 'ObjectPattern') {
// var {PropTypes} = React;
// Most of these cases will just be looking at {PropTypes} so this
// is usually a no-op.
var coreProperties = [];
var domProperties = [];
pattern.properties.forEach(function(prop) {
if (prop.key.type === 'Identifier') {
var key = prop.key.name;
if (CORE_PROPERTIES.indexOf(key) !== -1) {
coreProperties.push(prop);
} else if (DOM_PROPERTIES.indexOf(key) !== -1) {
domProperties.push(prop);
} else {
throw new Error(
'Unknown property React.' + key + ' while destructuring'
);
}
} else {
throw new Error('unimplemented');
}
});
var domDeclarator = j.variableDeclarator(
j.objectPattern(domProperties),
j.identifier('ReactDOM')
);
if (coreProperties.length && !domProperties.length) {
// nothing to do
coreUses++;
} else if (domProperties.length && !coreProperties.length) {
domUses++;
j(p.parent).replaceWith(domDeclarator);
} else {
coreUses++;
domUses++;
var decl = j(p).closest(j.VariableDeclaration);
decl.insertAfter(j.variableDeclaration(
decl.get().value.kind,
[domDeclarator]
));
}
} else {
throw new Error('unimplemented');
}
} else {
throw new Error('unimplemented');
}
} else if (p.parent.value.type === 'AssignmentExpression') {
if (p.parent.value.left === p.value) {
if (isRequire(p.parent.get('right'), coreModuleName)) {
requireAssignments.push(p.parent);
} else {
reportError(
p.parent.value,
'Unexpected assignment to ' + coreModuleName
);
}
} else {
throw new Error('unimplemented');
}
} else {
reportError(p.value, 'unimplemented ' + p.parent.value.type);
}
});
coreUses += root.find(j.JSXElement).size();
function insertRequire(name, path) {
var req = j.callExpression(
j.identifier('require'),
[j.literal(path)]
);
requireAssignments.forEach(function(requireAssignment) {
requireAssignment.parent.insertAfter(
j.expressionStatement(
j.assignmentExpression('=', j.identifier(name), req)
)
);
});
coreRequireDeclarator.parent.insertAfter(j.variableDeclaration(
coreRequireDeclarator.parent.value.kind,
[j.variableDeclarator(
j.identifier(name),
coreRequireDeclarator.value.init ? req : null
)]
));
}
if (domServerUses > 0 && !domServerAlreadyDeclared) {
insertRequire('ReactDOMServer', domServerModuleName);
}
if (domUses > 0 && !domAlreadyDeclared) {
insertRequire('ReactDOM', domModuleName);
}
if ((domUses > 0 || domServerUses > 0) && coreUses === 0) {
j(coreRequireDeclarator).remove();
requireAssignments.forEach(r => j(r).remove());
}
});
return root.toSource({quote: 'single'});
};

View File

@ -1,160 +0,0 @@
/**
* Copyright 2013-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';
module.exports = function(j) {
const REACT_CREATE_CLASS_MEMBER_EXPRESSION = {
type: 'MemberExpression',
object: {
name: 'React',
},
property: {
name: 'createClass',
},
};
// ---------------------------------------------------------------------------
// Checks if the file requires a certain module
const hasModule = (path, module) =>
path
.findVariableDeclarators()
.filter(j.filters.VariableDeclarator.requiresModule(module))
.size() === 1 ||
path
.find(j.ImportDeclaration, {
type: 'ImportDeclaration',
source: {
type: 'Literal',
},
})
.filter(declarator => declarator.value.source.value === module)
.size() === 1;
const hasReact = path => (
hasModule(path, 'React') ||
hasModule(path, 'react') ||
hasModule(path, 'react/addons')
);
// ---------------------------------------------------------------------------
// Finds all variable declarations that call React.createClass
const findReactCreateClassCallExpression = path =>
j(path).find(j.CallExpression, {
callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION,
});
const findReactCreateClass = path =>
path
.findVariableDeclarators()
.filter(decl => findReactCreateClassCallExpression(decl).size() > 0);
const findReactCreateClassExportDefault = path =>
path.find(j.ExportDefaultDeclaration, {
declaration: {
type: 'CallExpression',
callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION,
},
});
const findReactCreateClassModuleExports = path =>
path
.find(j.AssignmentExpression, {
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'module',
},
property: {
type: 'Identifier',
name: 'exports',
},
},
right: {
type: 'CallExpression',
callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION,
},
});
// ---------------------------------------------------------------------------
// Finds all classes that extend React.Component
const findReactES6ClassDeclaration = path =>
path
.find(j.ClassDeclaration, {
superClass: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'React',
},
property: {
type: 'Identifier',
name: 'Component',
},
},
});
// ---------------------------------------------------------------------------
// Checks if the React class has mixins
const isMixinProperty = property => {
const key = property.key;
const value = property.value;
return (
key.name === 'mixins' &&
value.type === 'ArrayExpression' &&
Array.isArray(value.elements) &&
value.elements.length
);
};
const hasMixins = classPath => {
const spec = getReactCreateClassSpec(classPath);
return spec && spec.properties.some(isMixinProperty);
};
// ---------------------------------------------------------------------------
// Others
const getReactCreateClassSpec = classPath => {
var {value} = classPath;
const spec = (value.init || value.right || value.declaration).arguments[0];
if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) {
return spec;
}
};
const createCreateReactClassCallExpression = properties =>
j.callExpression(
j.memberExpression(
j.identifier('React'),
j.identifier('createClass'),
false
),
[j.objectExpression(properties)]
);
const getComponentName =
classPath => classPath.node.id && classPath.node.id.name;
return {
createCreateReactClassCallExpression,
findReactES6ClassDeclaration,
findReactCreateClass,
findReactCreateClassCallExpression,
findReactCreateClassModuleExports,
findReactCreateClassExportDefault,
getComponentName,
getReactCreateClassSpec,
hasMixins,
hasModule,
hasReact,
isMixinProperty,
};
};

View File

@ -1,46 +0,0 @@
/**
* Copyright 2013-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.
*
*/
/*eslint-disable no-extend-native*/
'use strict';
function findIndex(predicate, context) {
if (this == null) {
throw new TypeError(
'Array.prototype.findIndex called on null or undefined'
);
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
for (var i = 0; i < length; i++) {
if (predicate.call(context, list[i], i, list)) {
return i;
}
}
return -1;
}
if (!Array.prototype.findIndex) {
Array.prototype.findIndex = findIndex;
}
if (!Array.prototype.find) {
Array.prototype.find = function(predicate, context) {
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
var index = findIndex.call(this, predicate, context);
return index === -1 ? undefined : this[index];
};
}