Merge pull request #5217 from zpao/rm-react-codemod

Move react-codemod to standalone repo
This commit is contained in:
Paul O’Shannessy
2015-10-20 15:46:49 -07:00
30 changed files with 4 additions and 2299 deletions

View File

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

View File

@@ -57,7 +57,7 @@ These builds are also available in the `react` package on bower.
ReactDOM.render(<MyComponent />, node);
```
Weve published the [automated codemod script](https://github.com/facebook/react/blob/master/packages/react-codemod/README.md) we used at Facebook to help you with this transition.
Weve published the [automated codemod script](https://github.com/reactjs/react-codemod/blob/master/README.md) we used at Facebook to help you with this transition.
The add-ons have moved to separate packages as well: `react-addons-clone-with-props`, `react-addons-create-fragment`, `react-addons-css-transition-group`, `react-addons-linked-state-mixin`, `react-addons-perf`, `react-addons-pure-render-mixin`, `react-addons-shallow-compare`, `react-addons-test-utils`, `react-addons-transition-group`, and `react-addons-update`, plus `ReactDOM.unstable_batchedUpdates` in `react-dom`.

View File

@@ -15,7 +15,7 @@ Like always, we have a few breaking changes in this release. We know changes can
If your code is free of warnings when running under React 0.13, upgrading should be easy. We have two new small breaking changes that didn't give a warning in 0.13 (see below). Every new change in 0.14, including the major changes below, is introduced with a runtime warning and will work as before until 0.15, so you don't have to worry about your app breaking with this upgrade.
For the two major changes which require significant code changes, we've included [codemod scripts](https://github.com/facebook/react/blob/master/packages/react-codemod/README.md) to help you upgrade your code automatically.
For the two major changes which require significant code changes, we've included [codemod scripts](https://github.com/reactjs/react-codemod/blob/master/README.md) to help you upgrade your code automatically.
See the changelog below for more details.
@@ -66,7 +66,7 @@ If you cant use `npm` yet, we provide pre-built browser builds for your conve
ReactDOM.render(<MyComponent />, node);
```
The old names will continue to work with a warning until 0.15 is released, and weve published the [automated codemod script](https://github.com/facebook/react/blob/master/packages/react-codemod/README.md) we used at Facebook to help you with this transition.
The old names will continue to work with a warning until 0.15 is released, and weve published the [automated codemod script](https://github.com/reactjs/react-codemod/blob/master/README.md) we used at Facebook to help you with this transition.
The add-ons have moved to separate packages as well:
- `react-addons-clone-with-props`
@@ -161,7 +161,7 @@ Each of these changes will continue to work as before with a new warning until t
- Due to the DOM node refs change mentioned above, `this.getDOMNode()` is now deprecated and `ReactDOM.findDOMNode(this)` can be used instead. Note that in most cases, calling `findDOMNode` is now unnecessary see the example above in the “DOM node refs” section.
With each returned DOM node, we've added a `getDOMNode` method for backwards compatibility that will work with a warning until 0.15. If you have a large codebase, you can use our [automated codemod script](https://github.com/facebook/react/blob/master/packages/react-codemod/README.md) to change your code automatically.
With each returned DOM node, we've added a `getDOMNode` method for backwards compatibility that will work with a warning until 0.15. If you have a large codebase, you can use our [automated codemod script](https://github.com/reactjs/react-codemod/blob/master/README.md) to change your code automatically.
- `setProps` and `replaceProps` are now deprecated. Instead, call ReactDOM.render again at the top level with the new props.
- ES6 component classes must now extend `React.Component` in order to enable stateless function components. The [ES3 module pattern](/react/blog/2015/01/27/react-v0.13.0-beta-1.html#other-languages) will continue to work.

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];
};
}