-
Notifications
You must be signed in to change notification settings - Fork 47.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add npm-react-codemod #3506
Merged
Merged
Add npm-react-codemod #3506
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e361fcb
Add skeleton for react-codemod
cpojer 328274b
Add ReactUtils and array polyfills.
cpojer 20004e9
Add findDOMNode transform
cpojer d4cb253
Add pure-render-mixin transform
cpojer d9c13c7
Add ES6 class transform
cpojer 1865c04
Update README for npm-react-codemod
cpojer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/transforms/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
## react-codemod | ||
|
||
This repository contains a collection of codemod scripts based on | ||
[JSCodeshift](https://github.com/facebook/jscodeshift) that help update React | ||
APIs. | ||
|
||
### Setup & Run | ||
|
||
* `npm install -g react-codemod` | ||
* `react-codemod <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. | ||
|
||
* `react-codemod findDOMNode <file>` | ||
|
||
`pure-render-mixin` removes `PureRenderMixin` and inlines | ||
`shouldComponentUpdate` so that the ES6 class transform can pick up the React | ||
component and turn it into an ES6 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` | ||
|
||
* `react-codemod pure-render-mixin <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 ES6 classes. | ||
|
||
* `react-codemod class <file>` | ||
* If `--no-super-class=true` 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. | ||
|
||
All scripts take an option `--no-explicit-require=true` if you don't have a | ||
`require('React')` statement in your code files and if you access React as a | ||
global. | ||
|
||
### Explanation of the ES6 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=true` 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 | ||
|
||
* `react-codemod class <file> --printOptions='{"quote":"double"}'` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "react-codemod", | ||
"version": "1.0.0", | ||
"description": "React codemod scripts", | ||
"license": "BSD-3-Clause", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/facebook/react" | ||
}, | ||
"scripts": { | ||
"build": "rm -rf build; babel transforms/ --out-dir=build/", | ||
"test": "jest", | ||
"prepublish": "npm run build" | ||
}, | ||
"bin": { | ||
"react-codemod": "./react-codemod" | ||
}, | ||
"dependencies": { | ||
"jscodeshift": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"babel": "^4.7.16", | ||
"babel-jest": "^4.0.0", | ||
"jest-cli": "^0.4.0" | ||
}, | ||
"jest": { | ||
"scriptPreprocessor": "./node_modules/babel-jest", | ||
"testPathDirs": [ | ||
"test" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/bin/bash | ||
|
||
DIR=$(npm root -g)/react-codemod | ||
TRANSFORM=$1 | ||
shift | ||
|
||
$DIR/node_modules/.bin/jscodeshift -t $DIR/build/$TRANSFORM.js $@ | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright (c) 2015-present, 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', { | ||
'no-super-class': true, | ||
}); | ||
|
||
test('class', 'class-test3'); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
'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} | ||
/> | ||
); | ||
} | ||
}); | ||
|
||
module.exports = Relay.createContainer(MyComponent, { | ||
queries: { | ||
me: Relay.graphql`this is not graphql`, | ||
} | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this isn't going to work on Windows. I thought we were going to make this a js script that used node to shell out to jscodeshift?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh, that's no fun. Let me see what I can do.