From 2a509a627e5750839f3ae58f6ed48d654ce04d3f Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 8 Jun 2016 18:57:11 -0700 Subject: [PATCH 01/64] added boilerplates --- .../__testfixtures__/class-arrow-test2.js | 20 + .../class-arrow-test2.output.js | 22 + .../__testfixtures__/class-arrow.input.js | 133 +++++ .../__testfixtures__/class-arrow.output.js | 144 +++++ .../export-default-class-arrow.input.js | 21 + .../export-default-class-arrow.output.js | 23 + transforms/__tests__/class-arrow-test.js | 15 + transforms/class-arrow.js | 525 ++++++++++++++++++ 8 files changed, 903 insertions(+) create mode 100644 transforms/__testfixtures__/class-arrow-test2.js create mode 100644 transforms/__testfixtures__/class-arrow-test2.output.js create mode 100644 transforms/__testfixtures__/class-arrow.input.js create mode 100644 transforms/__testfixtures__/class-arrow.output.js create mode 100644 transforms/__testfixtures__/export-default-class-arrow.input.js create mode 100644 transforms/__testfixtures__/export-default-class-arrow.output.js create mode 100644 transforms/__tests__/class-arrow-test.js create mode 100644 transforms/class-arrow.js diff --git a/transforms/__testfixtures__/class-arrow-test2.js b/transforms/__testfixtures__/class-arrow-test2.js new file mode 100644 index 00000000..b0f04327 --- /dev/null +++ b/transforms/__testfixtures__/class-arrow-test2.js @@ -0,0 +1,20 @@ +'use strict'; + +var React = require('React'); + +// Comment +module.exports = React.createClass({ + propTypes: { + foo: React.PropTypes.bool, + }, + + getInitialState: function() { + return { + foo: 'bar', + }; + }, + + render: function() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class-arrow-test2.output.js b/transforms/__testfixtures__/class-arrow-test2.output.js new file mode 100644 index 00000000..68803e41 --- /dev/null +++ b/transforms/__testfixtures__/class-arrow-test2.output.js @@ -0,0 +1,22 @@ +'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
; + } +}; + +module.exports.propTypes = { + foo: React.PropTypes.bool, +}; diff --git a/transforms/__testfixtures__/class-arrow.input.js b/transforms/__testfixtures__/class-arrow.input.js new file mode 100644 index 00000000..924ce77f --- /dev/null +++ b/transforms/__testfixtures__/class-arrow.input.js @@ -0,0 +1,133 @@ +'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 ; + }, + + _renderImageRange: function(text, range) { + var image = range.image; + if (image) { + return ( + + ); + } + }, + + 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 = + + {text} + ; + } else { + text = {text}; + } + + return text; + }, + + /* This is a comment */ + render: function() { + var content = this.props.text; + return ( + + ); + }, +}); + +var MyComponent4 = React.createClass({ + foo: callMeMaybe(), + render: function() {}, +}); + +module.exports = Relay.createContainer(MyComponent, { + queries: { + me: Relay.graphql`this is not graphql`, + }, +}); diff --git a/transforms/__testfixtures__/class-arrow.output.js b/transforms/__testfixtures__/class-arrow.output.js new file mode 100644 index 00000000..fedb3bb6 --- /dev/null +++ b/transforms/__testfixtures__/class-arrow.output.js @@ -0,0 +1,144 @@ +'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 ; + } + + _renderImageRange(text, range) { + var image = range.image; + if (image) { + return ( + + ); + } + } + + 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 = + + {text} + ; + } else { + text = {text}; + } + + return text; + } + + /* This is a comment */ + render() { + var content = this.props.text; + return ( + + ); + } +} + +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`, + }, +}); diff --git a/transforms/__testfixtures__/export-default-class-arrow.input.js b/transforms/__testfixtures__/export-default-class-arrow.input.js new file mode 100644 index 00000000..148843b1 --- /dev/null +++ b/transforms/__testfixtures__/export-default-class-arrow.input.js @@ -0,0 +1,21 @@ +/*eslint-disable no-extra-semi*/ + +'use strict'; + +import React from 'React'; + +export default React.createClass({ + getInitialState: function() { + return { + foo: 'bar', + }; + }, + + propTypes: { + foo: React.PropTypes.string, + }, + + render: function() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/export-default-class-arrow.output.js b/transforms/__testfixtures__/export-default-class-arrow.output.js new file mode 100644 index 00000000..21bc58c9 --- /dev/null +++ b/transforms/__testfixtures__/export-default-class-arrow.output.js @@ -0,0 +1,23 @@ +/*eslint-disable no-extra-semi*/ + +'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
; + } +}; diff --git a/transforms/__tests__/class-arrow-test.js b/transforms/__tests__/class-arrow-test.js new file mode 100644 index 00000000..a81d9488 --- /dev/null +++ b/transforms/__tests__/class-arrow-test.js @@ -0,0 +1,15 @@ +/** + * 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'; + +const defineTest = require('jscodeshift/dist/testUtils').defineTest; +defineTest(__dirname, 'class-arrow'); +defineTest(__dirname, 'class-arrow', null, 'export-default-class-arrow'); diff --git a/transforms/class-arrow.js b/transforms/class-arrow.js new file mode 100644 index 00000000..4ddff794 --- /dev/null +++ b/transforms/class-arrow.js @@ -0,0 +1,525 @@ +/** + * 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 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)); + + 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.` + findAutobindNamesFor(classPath, fnNames, j.thisExpression()).forEach(add); + + // Find `self.` 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 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 = (hasPropsAccess) => { + return [j.identifier('props'), j.identifier('context')]; + }; + + const createConstructor = ( + getInitialState, + autobindFunctions + ) => { + if (!getInitialState && !autobindFunctions.length) { + return []; + } + + const hasPropsAccess = updatePropsAccess(getInitialState); + return [ + createMethodDefinition({ + key: j.identifier('constructor'), + value: j.functionExpression( + null, + createConstructorArgs(hasPropsAccess), + j.blockStatement( + [].concat( + [ + j.expressionStatement( + j.callExpression( + j.identifier('super'), + [j.identifier('props'), j.identifier('context')] + ) + ), + ], + autobindFunctions.map(createBindAssignment), + inlineGetInitialState(getInitialState) + ) + ) + ), + }), + ]; + }; + + const createESClass = ( + name, + properties, + getInitialState, + autobindFunctions, + comments + ) => + withComments(j.classDeclaration( + name ? j.identifier(name) : null, + j.classBody( + [].concat( + createConstructor( + getInitialState, + autobindFunctions + ), + properties + ) + ), + j.memberExpression( + j.identifier('React'), + j.identifier('Component'), + false + ) + ), {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, 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 + ) + ); + + 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'] === false || ReactUtils.hasReact(root) + ) { + const apply = (path, type) => + path + .filter(hasMixins) + .filter(callsDeprecatedAPIs) + .filter(canConvertToClass) + .forEach(classPath => updateToClass(classPath, 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; +}; From eacb1e1089f66f09dd77ad022370f304c0d1330e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 9 Jun 2016 16:07:31 -0700 Subject: [PATCH 02/64] renamed to avoid conflict --- ....input.js => export-default-property-initializer.input.js} | 0 ...utput.js => export-default-property-initializer.output.js} | 0 .../{class-arrow-test2.js => property-initializer-test2.js} | 0 ...w-test2.output.js => property-initializer-test2.output.js} | 0 .../{class-arrow.input.js => property-initializer.input.js} | 0 .../{class-arrow.output.js => property-initializer.output.js} | 0 .../{class-arrow-test.js => property-initializer-test.js} | 4 ++-- transforms/{class-arrow.js => property-initializer.js} | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename transforms/__testfixtures__/{export-default-class-arrow.input.js => export-default-property-initializer.input.js} (100%) rename transforms/__testfixtures__/{export-default-class-arrow.output.js => export-default-property-initializer.output.js} (100%) rename transforms/__testfixtures__/{class-arrow-test2.js => property-initializer-test2.js} (100%) rename transforms/__testfixtures__/{class-arrow-test2.output.js => property-initializer-test2.output.js} (100%) rename transforms/__testfixtures__/{class-arrow.input.js => property-initializer.input.js} (100%) rename transforms/__testfixtures__/{class-arrow.output.js => property-initializer.output.js} (100%) rename transforms/__tests__/{class-arrow-test.js => property-initializer-test.js} (73%) rename transforms/{class-arrow.js => property-initializer.js} (100%) diff --git a/transforms/__testfixtures__/export-default-class-arrow.input.js b/transforms/__testfixtures__/export-default-property-initializer.input.js similarity index 100% rename from transforms/__testfixtures__/export-default-class-arrow.input.js rename to transforms/__testfixtures__/export-default-property-initializer.input.js diff --git a/transforms/__testfixtures__/export-default-class-arrow.output.js b/transforms/__testfixtures__/export-default-property-initializer.output.js similarity index 100% rename from transforms/__testfixtures__/export-default-class-arrow.output.js rename to transforms/__testfixtures__/export-default-property-initializer.output.js diff --git a/transforms/__testfixtures__/class-arrow-test2.js b/transforms/__testfixtures__/property-initializer-test2.js similarity index 100% rename from transforms/__testfixtures__/class-arrow-test2.js rename to transforms/__testfixtures__/property-initializer-test2.js diff --git a/transforms/__testfixtures__/class-arrow-test2.output.js b/transforms/__testfixtures__/property-initializer-test2.output.js similarity index 100% rename from transforms/__testfixtures__/class-arrow-test2.output.js rename to transforms/__testfixtures__/property-initializer-test2.output.js diff --git a/transforms/__testfixtures__/class-arrow.input.js b/transforms/__testfixtures__/property-initializer.input.js similarity index 100% rename from transforms/__testfixtures__/class-arrow.input.js rename to transforms/__testfixtures__/property-initializer.input.js diff --git a/transforms/__testfixtures__/class-arrow.output.js b/transforms/__testfixtures__/property-initializer.output.js similarity index 100% rename from transforms/__testfixtures__/class-arrow.output.js rename to transforms/__testfixtures__/property-initializer.output.js diff --git a/transforms/__tests__/class-arrow-test.js b/transforms/__tests__/property-initializer-test.js similarity index 73% rename from transforms/__tests__/class-arrow-test.js rename to transforms/__tests__/property-initializer-test.js index a81d9488..8cf67c92 100644 --- a/transforms/__tests__/class-arrow-test.js +++ b/transforms/__tests__/property-initializer-test.js @@ -11,5 +11,5 @@ 'use strict'; const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'class-arrow'); -defineTest(__dirname, 'class-arrow', null, 'export-default-class-arrow'); +defineTest(__dirname, 'property-initializer'); +defineTest(__dirname, 'property-initializer', null, 'export-default-property-initializer'); diff --git a/transforms/class-arrow.js b/transforms/property-initializer.js similarity index 100% rename from transforms/class-arrow.js rename to transforms/property-initializer.js From 2c5b461329653854d97f5ae5705178539b61e403 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 9 Jun 2016 19:52:10 -0700 Subject: [PATCH 03/64] added a failing test --- ...-test2.js => property-initializer-2.input.js} | 0 ...utput.js => property-initializer-2.output.js} | 8 ++++---- .../property-initializer.input.js | 16 ++++++++-------- .../property-initializer.output.js | 16 ++++++++-------- .../__tests__/property-initializer-test.js | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) rename transforms/__testfixtures__/{property-initializer-test2.js => property-initializer-2.input.js} (100%) rename transforms/__testfixtures__/{property-initializer-test2.output.js => property-initializer-2.output.js} (80%) diff --git a/transforms/__testfixtures__/property-initializer-test2.js b/transforms/__testfixtures__/property-initializer-2.input.js similarity index 100% rename from transforms/__testfixtures__/property-initializer-test2.js rename to transforms/__testfixtures__/property-initializer-2.input.js diff --git a/transforms/__testfixtures__/property-initializer-test2.output.js b/transforms/__testfixtures__/property-initializer-2.output.js similarity index 80% rename from transforms/__testfixtures__/property-initializer-test2.output.js rename to transforms/__testfixtures__/property-initializer-2.output.js index 68803e41..96ae4636 100644 --- a/transforms/__testfixtures__/property-initializer-test2.output.js +++ b/transforms/__testfixtures__/property-initializer-2.output.js @@ -4,6 +4,10 @@ var React = require('React'); // Comment module.exports = class extends React.Component { + static propTypes = { + foo: React.PropTypes.bool, + }; + constructor(props, context) { super(props, context); @@ -16,7 +20,3 @@ module.exports = class extends React.Component { return
; } }; - -module.exports.propTypes = { - foo: React.PropTypes.bool, -}; diff --git a/transforms/__testfixtures__/property-initializer.input.js b/transforms/__testfixtures__/property-initializer.input.js index 924ce77f..440f9a17 100644 --- a/transforms/__testfixtures__/property-initializer.input.js +++ b/transforms/__testfixtures__/property-initializer.input.js @@ -16,7 +16,7 @@ var MyComponent = React.createClass({ }; }, - foo: function() { + foo: function(): void { this.setState({heyoo: 24}); }, }); @@ -26,7 +26,7 @@ var MyComponent2 = React.createClass({ getDefaultProps: function() { return {a: 1}; }, - foo: function() { + foo: function(): void { pass(this.foo); this.forceUpdate(); }, @@ -35,7 +35,7 @@ var MyComponent2 = React.createClass({ var MyComponent3 = React.createClass({ statics: { someThing: 10, - foo: function() {}, + funcThatDoesNothing: function(): void {}, }, propTypes: { highlightEntities: React.PropTypes.bool, @@ -47,7 +47,7 @@ var MyComponent3 = React.createClass({ }, getDefaultProps: function() { - foo(); + unboundFunc(); return { linkifyEntities: true, highlightEntities: false, @@ -61,11 +61,11 @@ var MyComponent3 = React.createClass({ }; }, - _renderText: function(text) { + _renderText: function(text: string): ReactElement { return ; }, - _renderImageRange: function(text, range) { + _renderImageRange: function(text: string, range): ReactElement { var image = range.image; if (image) { return ( @@ -79,10 +79,10 @@ var MyComponent3 = React.createClass({ }, autobindMe: function() {}, - dontAutobindMe: function() {}, + dontAutobindMe: function(): number { return 12; }, // Function comment - _renderRange: function(text, range) { + _renderRange: function(text: string, range, bla: Promise): ReactElement { var self = this; self.dontAutobindMe(); diff --git a/transforms/__testfixtures__/property-initializer.output.js b/transforms/__testfixtures__/property-initializer.output.js index fedb3bb6..e491844e 100644 --- a/transforms/__testfixtures__/property-initializer.output.js +++ b/transforms/__testfixtures__/property-initializer.output.js @@ -18,7 +18,7 @@ class MyComponent extends React.Component { }; } - foo() { + foo(): void { this.setState({heyoo: 24}); } } @@ -30,7 +30,7 @@ class MyComponent2 extends React.Component { this.foo = this.foo.bind(this); } - foo() { + foo(): void { pass(this.foo); this.forceUpdate(); } @@ -51,11 +51,11 @@ class MyComponent3 extends React.Component { }; } - _renderText(text) { + _renderText(text: string): ReactElement { return ; } - _renderImageRange(text, range) { + _renderImageRange(text: string, range): ReactElement { var image = range.image; if (image) { return ( @@ -69,10 +69,10 @@ class MyComponent3 extends React.Component { } autobindMe() {} - dontAutobindMe() {} + dontAutobindMe(): number { return 12; } // Function comment - _renderRange(text, range) { + _renderRange(text: string, range, bla: Promise): ReactElement { var self = this; self.dontAutobindMe(); @@ -112,14 +112,14 @@ class MyComponent3 extends React.Component { } MyComponent3.defaultProps = function() { - foo(); + unboundFunc(); return { linkifyEntities: true, highlightEntities: false, }; }(); -MyComponent3.foo = function() {}; +MyComponent3.funcThatDoesNothing = function(): void {}; MyComponent3.propTypes = { highlightEntities: React.PropTypes.bool, diff --git a/transforms/__tests__/property-initializer-test.js b/transforms/__tests__/property-initializer-test.js index 8cf67c92..0525e654 100644 --- a/transforms/__tests__/property-initializer-test.js +++ b/transforms/__tests__/property-initializer-test.js @@ -11,5 +11,5 @@ 'use strict'; const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'property-initializer'); -defineTest(__dirname, 'property-initializer', null, 'export-default-property-initializer'); +// defineTest(__dirname, 'property-initializer'); +defineTest(__dirname, 'property-initializer', null, 'property-initializer-2'); From 6d9e3cb217de0394b599eecd430f87422a50915f Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 13 Jun 2016 00:08:02 -0700 Subject: [PATCH 04/64] added initial working version --- README.md | 50 +++++ .../property-initializer-2.input.js | 30 ++- .../property-initializer-2.output.js | 34 ++- .../property-initializer.input.js | 8 +- .../property-initializer.output.js | 99 ++++----- .../__tests__/property-initializer-test.js | 2 +- transforms/property-initializer.js | 203 +++++++++--------- 7 files changed, 261 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index 91d23aec..6c73d017 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,56 @@ The constructor logic is as follows: * Changes `return StateObject` from `getInitialState` to assign `this.state` directly. +### Explanation of the new ES2015 class transform with property initializers + + * 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 transforms them to `static` properties at the very top. + like `static displayName = 'Counter';` + * TODO do we bind stuff in the `static` object? + * 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 + `static defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than + one statement it will transform into a self-invoking function like this: + `static 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. + * Transforms class methods to arrow functions as class property initializers + (i.e., to bind them) 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 (lifecycle methods) and ignores functions that are being + called directly (unless it is both called directly and passed around to + somewhere else) + * TODO how do we handle `getInitialState`? + * If we reference `this.props` in `getInitialState` then it + has to be in the constructor + * Otherwise it's simple and just make it a property initializer + * TODO [???] 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 diff --git a/transforms/__testfixtures__/property-initializer-2.input.js b/transforms/__testfixtures__/property-initializer-2.input.js index b0f04327..6aafd491 100644 --- a/transforms/__testfixtures__/property-initializer-2.input.js +++ b/transforms/__testfixtures__/property-initializer-2.input.js @@ -2,15 +2,41 @@ var React = require('React'); +var ComponentWithNonSimpleInitialState = React.createClass({ + statics: { + iDontKnowWhyYouNeedThis: true, // but comment it + foo: 'bar', + }, + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); + // Comment module.exports = React.createClass({ propTypes: { foo: React.PropTypes.bool, }, - getInitialState: function() { + getDefaultProps: function() { + return { + foo: 12, + }; + }, + + getInitialState: function() { // non-simple + var data = 'bar'; return { - foo: 'bar', + bar: data, }; }, diff --git a/transforms/__testfixtures__/property-initializer-2.output.js b/transforms/__testfixtures__/property-initializer-2.output.js index 96ae4636..98e748cb 100644 --- a/transforms/__testfixtures__/property-initializer-2.output.js +++ b/transforms/__testfixtures__/property-initializer-2.output.js @@ -2,20 +2,42 @@ var React = require('React'); -// Comment -module.exports = class extends React.Component { - static propTypes = { - foo: React.PropTypes.bool, - }; +class ComponentWithNonSimpleInitialState extends React.Component { + static foo = 'bar'; + static iDontKnowWhyYouNeedThis = true; // but comment it constructor(props, context) { super(props, context); this.state = { - foo: 'bar', + counter: props.initialNumber + 1, }; } + render() { + return ( +
{this.state.counter}
+ ); + } +} + +// Comment +module.exports = class extends React.Component { + static defaultProps = { + foo: 12, + }; + + static propTypes = { + foo: React.PropTypes.bool, + }; + + state = function() { // non-simple + var data = 'bar'; + return { + bar: data, + }; + }(); + render() { return
; } diff --git a/transforms/__testfixtures__/property-initializer.input.js b/transforms/__testfixtures__/property-initializer.input.js index 440f9a17..813f82d5 100644 --- a/transforms/__testfixtures__/property-initializer.input.js +++ b/transforms/__testfixtures__/property-initializer.input.js @@ -26,7 +26,7 @@ var MyComponent2 = React.createClass({ getDefaultProps: function() { return {a: 1}; }, - foo: function(): void { + foo: function() { // flow annotations dont work for now pass(this.foo); this.forceUpdate(); }, @@ -61,11 +61,11 @@ var MyComponent3 = React.createClass({ }; }, - _renderText: function(text: string): ReactElement { + _renderText: function(text: string) { // TODO no return type yet return ; }, - _renderImageRange: function(text: string, range): ReactElement { + _renderImageRange: function(text: string, range) { // TODO no return type yet var image = range.image; if (image) { return ( @@ -82,7 +82,7 @@ var MyComponent3 = React.createClass({ dontAutobindMe: function(): number { return 12; }, // Function comment - _renderRange: function(text: string, range, bla: Promise): ReactElement { + _renderRange: function(text: string, range, bla: Promise) { var self = this; self.dontAutobindMe(); diff --git a/transforms/__testfixtures__/property-initializer.output.js b/transforms/__testfixtures__/property-initializer.output.js index e491844e..fb359d0b 100644 --- a/transforms/__testfixtures__/property-initializer.output.js +++ b/transforms/__testfixtures__/property-initializer.output.js @@ -25,25 +25,38 @@ class MyComponent extends React.Component { // Class comment class MyComponent2 extends React.Component { - constructor(props, context) { - super(props, context); - this.foo = this.foo.bind(this); - } + static defaultProps = {a: 1}; - foo(): void { + foo = () => { // flow annotations dont work for now pass(this.foo); this.forceUpdate(); - } + }; } -MyComponent2.defaultProps = {a: 1}; - class MyComponent3 extends React.Component { + static defaultProps = function() { + unboundFunc(); + return { + linkifyEntities: true, + highlightEntities: false, + }; + }(); + + static funcThatDoesNothing = function(): void {}; + + static propTypes = { + highlightEntities: React.PropTypes.bool, + linkifyEntities: React.PropTypes.bool, + text: React.PropTypes.shape({ + text: React.PropTypes.string, + ranges: React.PropTypes.array, + }).isRequired, + }; + + static someThing = 10; + 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 = { @@ -51,28 +64,8 @@ class MyComponent3 extends React.Component { }; } - _renderText(text: string): ReactElement { - return ; - } - - _renderImageRange(text: string, range): ReactElement { - var image = range.image; - if (image) { - return ( - - ); - } - } - - autobindMe() {} - dontAutobindMe(): number { return 12; } - // Function comment - _renderRange(text: string, range, bla: Promise): ReactElement { + _renderRange = (text: string, range, bla: Promise) => { var self = this; self.dontAutobindMe(); @@ -95,8 +88,29 @@ class MyComponent3 extends React.Component { } return text; + }; + + _renderText = (text: string) => { // TODO no return type yet + return ; + }; + + autobindMe = () => {}; + + _renderImageRange(text: string, range) { // TODO no return type yet + var image = range.image; + if (image) { + return ( + + ); + } } + dontAutobindMe(): number { return 12; } + /* This is a comment */ render() { var content = this.props.text; @@ -111,27 +125,6 @@ class MyComponent3 extends React.Component { } } -MyComponent3.defaultProps = function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; -}(); - -MyComponent3.funcThatDoesNothing = function(): void {}; - -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() {}, diff --git a/transforms/__tests__/property-initializer-test.js b/transforms/__tests__/property-initializer-test.js index 0525e654..6972b4ee 100644 --- a/transforms/__tests__/property-initializer-test.js +++ b/transforms/__tests__/property-initializer-test.js @@ -11,5 +11,5 @@ 'use strict'; const defineTest = require('jscodeshift/dist/testUtils').defineTest; -// defineTest(__dirname, 'property-initializer'); +defineTest(__dirname, 'property-initializer'); defineTest(__dirname, 'property-initializer', null, 'property-initializer-2'); diff --git a/transforms/property-initializer.js b/transforms/property-initializer.js index 4ddff794..360ce375 100644 --- a/transforms/property-initializer.js +++ b/transforms/property-initializer.js @@ -150,6 +150,9 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); + // - collects everything in the `statics` property object + // - `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` + // - simplifies `getDefaultProps` or converts it to an IIFE const collectStatics = specPath => { const statics = specPath.properties.find(createFindPropFn('statics')); const staticsObject = @@ -164,7 +167,7 @@ module.exports = (file, api, options) => { staticsObject.concat(specPath.properties.filter(property => property.key && STATIC_KEYS[property.key.name] )) - .sort((a, b) => a.key.name < b.key.name) + .sort((a, b) => a.key.name > b.key.name) ); }; @@ -172,7 +175,7 @@ module.exports = (file, api, options) => { .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) - .filter(isFunctionExpression); + .filter(isFunctionExpression); // TODO how about stuff that are not functions? const findAutobindNamesFor = (subtree, fnNames, literalOrIdentifier) => { const node = literalOrIdentifier; @@ -234,7 +237,7 @@ module.exports = (file, api, options) => { ).forEach(add) ); - return Object.keys(autobindNames).sort(); + return Object.keys(autobindNames).sort((a, b) => a > b); }; // --------------------------------------------------------------------------- @@ -246,31 +249,7 @@ module.exports = (file, api, options) => { 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 updatePropsAccess = getInitialState => + const isInitialStateSimple = getInitialState => getInitialState ? j(getInitialState) .find(j.MemberExpression, { @@ -282,16 +261,24 @@ module.exports = (file, api, options) => { name: 'props', }, }) - .forEach(path => j(path).replaceWith(j.identifier('props'))) - .size() > 0 : - false; + .size() === 0 : + true; - const inlineGetInitialState = getInitialState => { - if (!getInitialState) { - return []; - } + const updatePropsAccess = getInitialState => + j(getInitialState) + .find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'props', + }, + }) + .forEach(path => j(path).replaceWith(j.identifier('props'))); - return getInitialState.value.body.body.map(statement => { + const inlineGetInitialState = getInitialState => + getInitialState.value.body.body.map(statement => { if (statement.type === 'ReturnStatement') { return j.expressionStatement( j.assignmentExpression( @@ -308,27 +295,39 @@ module.exports = (file, api, options) => { return statement; }); + + const createInitialStateValue = value => { + if (hasSingleReturnStatement(value)) { + return value.body.body[0].argument; + } else { + return j.callExpression( + value, + [] + ); + } }; - const createConstructorArgs = (hasPropsAccess) => { + const convertInitialStateToClassProperty = getInitialState => + withComments(j.classProperty( + j.identifier('state'), + createInitialStateValue(getInitialState.value), + null, + false + ), getInitialState); + + const createConstructorArgs = () => { return [j.identifier('props'), j.identifier('context')]; }; - const createConstructor = ( - getInitialState, - autobindFunctions - ) => { - if (!getInitialState && !autobindFunctions.length) { - return []; - } + const createConstructor = getInitialState => { + updatePropsAccess(getInitialState); // TODO no side-effects? - const hasPropsAccess = updatePropsAccess(getInitialState); return [ createMethodDefinition({ key: j.identifier('constructor'), value: j.functionExpression( null, - createConstructorArgs(hasPropsAccess), + createConstructorArgs(), j.blockStatement( [].concat( [ @@ -339,7 +338,6 @@ module.exports = (file, api, options) => { ) ), ], - autobindFunctions.map(createBindAssignment), inlineGetInitialState(getInitialState) ) ) @@ -348,22 +346,66 @@ module.exports = (file, api, options) => { ]; }; + const createArrowFunctionExpression = fn => j.arrowFunctionExpression( + fn.params, + fn.body, + false + ); + + const createArrowPropertyFromMethod = method => + withComments(j.classProperty( + j.identifier(method.key.name), + createArrowFunctionExpression(method.value), + null, + false + ), method); + + // if there's no `getInitialState` or the `getInitialState` function is simple + // (i.e., it does not reference `this.props`) then we don't need a constructor. + // we can simply do `state = {...}` as a property initializer. + // otherwise, create a constructor and inline `this.state = ...`. const createESClass = ( name, - properties, + staticProperties, getInitialState, - autobindFunctions, + autobindFunctionNames, + methods, comments - ) => - withComments(j.classDeclaration( + ) => { + let newConstructor = []; + const newProperties = []; + + if (isInitialStateSimple(getInitialState)) { + if (getInitialState) { + newProperties.push(convertInitialStateToClassProperty(getInitialState)); + } + } else { + newConstructor = createConstructor(getInitialState); + } + + const arrowBindFunctions = []; + const newMethods = []; + + for (let i = 0; i < methods.length; i++) { + const method = methods[i]; + if (autobindFunctionNames.indexOf(method.key.name) !== -1) { + arrowBindFunctions.push(method); + } else { + newMethods.push(method); + } + } + + arrowBindFunctions.sort((a, b) => a.key.name > b.key.name); + + return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( [].concat( - createConstructor( - getInitialState, - autobindFunctions - ), - properties + staticProperties, + newConstructor, + newProperties, + arrowBindFunctions.map(createArrowPropertyFromMethod), + newMethods ) ), j.memberExpression( @@ -372,22 +414,7 @@ module.exports = (file, api, options) => { false ) ), {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( @@ -443,13 +470,6 @@ module.exports = (file, api, options) => { return null; }; - const createModuleExportsMemberExpression = () => - j.memberExpression( - j.identifier('module'), - j.identifier('exports'), - false - ); - const updateToClass = (classPath, type) => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const name = ReactUtils.getComponentName(classPath); @@ -457,12 +477,9 @@ module.exports = (file, api, options) => { const functions = collectFunctions(specPath); const comments = getComments(classPath); - const autobindFunctions = collectAutoBindFunctions(functions, classPath); + const autobindFunctionNames = 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); @@ -470,30 +487,18 @@ module.exports = (file, api, options) => { path = j(classPath).closest(j.VariableDeclaration); } - const properties = - (type == 'exportDefault') ? createStaticClassProperties(statics) : []; + const staticProperties = createStaticClassProperties(statics); path.replaceWith( createESClass( name, - properties.concat(functions.map(createMethodDefinition)), + staticProperties, getInitialState, - autobindFunctions, + autobindFunctionNames, + functions.map(createMethodDefinition), comments ) ); - - 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 ( From 00790d449d58338074388ea27c3e0ff467daad3c Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 13 Jun 2016 14:47:15 -0700 Subject: [PATCH 05/64] added support for mixins --- .../property-initializer-2.input.js | 34 ++++++ .../property-initializer-2.output.js | 44 +++++++- .../property-initializer.output.js | 31 +++--- transforms/property-initializer.js | 103 ++++++++++-------- transforms/utils/ReactUtils.js | 30 +++++ 5 files changed, 173 insertions(+), 69 deletions(-) diff --git a/transforms/__testfixtures__/property-initializer-2.input.js b/transforms/__testfixtures__/property-initializer-2.input.js index 6aafd491..634b85b1 100644 --- a/transforms/__testfixtures__/property-initializer-2.input.js +++ b/transforms/__testfixtures__/property-initializer-2.input.js @@ -1,6 +1,8 @@ 'use strict'; var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); +var FooBarMixin = require('FooBarMixin'); var ComponentWithNonSimpleInitialState = React.createClass({ statics: { @@ -44,3 +46,35 @@ module.exports = React.createClass({ return
; }, }); + +var ComponentWithOnlyPureRenderMixin = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); + +var ComponentWithInconvertibleMixins = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/property-initializer-2.output.js b/transforms/__testfixtures__/property-initializer-2.output.js index 98e748cb..dfd5855e 100644 --- a/transforms/__testfixtures__/property-initializer-2.output.js +++ b/transforms/__testfixtures__/property-initializer-2.output.js @@ -1,10 +1,12 @@ 'use strict'; var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); +var FooBarMixin = require('FooBarMixin'); class ComponentWithNonSimpleInitialState extends React.Component { - static foo = 'bar'; static iDontKnowWhyYouNeedThis = true; // but comment it + static foo = 'bar'; constructor(props, context) { super(props, context); @@ -23,14 +25,14 @@ class ComponentWithNonSimpleInitialState extends React.Component { // Comment module.exports = class extends React.Component { - static defaultProps = { - foo: 12, - }; - static propTypes = { foo: React.PropTypes.bool, }; + static defaultProps = { + foo: 12, + }; + state = function() { // non-simple var data = 'bar'; return { @@ -42,3 +44,35 @@ module.exports = class extends React.Component { return
; } }; + +class ComponentWithOnlyPureRenderMixin extends React.PureComponent { + constructor(props, context) { + super(props, context); + + this.state = { + counter: props.initialNumber + 1, + }; + } + + render() { + return ( +
{this.state.counter}
+ ); + } +} + +var ComponentWithInconvertibleMixins = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/property-initializer.output.js b/transforms/__testfixtures__/property-initializer.output.js index fb359d0b..2566a19f 100644 --- a/transforms/__testfixtures__/property-initializer.output.js +++ b/transforms/__testfixtures__/property-initializer.output.js @@ -34,16 +34,6 @@ class MyComponent2 extends React.Component { } class MyComponent3 extends React.Component { - static defaultProps = function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; - }(); - - static funcThatDoesNothing = function(): void {}; - static propTypes = { highlightEntities: React.PropTypes.bool, linkifyEntities: React.PropTypes.bool, @@ -53,7 +43,16 @@ class MyComponent3 extends React.Component { }).isRequired, }; + static defaultProps = function() { + unboundFunc(); + return { + linkifyEntities: true, + highlightEntities: false, + }; + }(); + static someThing = 10; + static funcThatDoesNothing = function(): void {}; constructor(props, context) { super(props, context); @@ -64,6 +63,12 @@ class MyComponent3 extends React.Component { }; } + _renderText = (text: string) => { // TODO no return type yet + return ; + }; + + autobindMe = () => {}; + // Function comment _renderRange = (text: string, range, bla: Promise) => { var self = this; @@ -90,12 +95,6 @@ class MyComponent3 extends React.Component { return text; }; - _renderText = (text: string) => { // TODO no return type yet - return ; - }; - - autobindMe = () => {}; - _renderImageRange(text: string, range) { // TODO no return type yet var image = range.image; if (image) { diff --git a/transforms/property-initializer.js b/transforms/property-initializer.js index 360ce375..eda3cd4d 100644 --- a/transforms/property-initializer.js +++ b/transforms/property-initializer.js @@ -54,6 +54,8 @@ module.exports = (file, api, options) => { propTypes: true, }; + const MIXIN_KEY = 'mixins'; + // --------------------------------------------------------------------------- // Checks if the module uses mixins or accesses deprecated APIs. const checkDeprecatedAPICalls = classPath => @@ -86,7 +88,8 @@ module.exports = (file, api, options) => { STATIC_KEY != prop.key.name && !filterDefaultPropsField(prop) && !filterGetInitialStateField(prop) && - !isFunctionExpression(prop) + !isFunctionExpression(prop) && + MIXIN_KEY != prop.key.name ) )); @@ -105,11 +108,14 @@ module.exports = (file, api, options) => { return !invalidProperties.length; }; - const hasMixins = classPath => { - if (ReactUtils.hasMixins(classPath)) { + const hasInconvertibleMixins = classPath => { + if ( + ReactUtils.hasMixins(classPath) && + !ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) + ) { console.log( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + - 'was skipped because of mixins.' + 'was skipped because of inconvertible mixins.' ); return false; } @@ -150,32 +156,32 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); - // - collects everything in the `statics` property object - // - `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` - // - simplifies `getDefaultProps` or converts it to an IIFE + // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` first; + // then simplifies `getDefaultProps` or converts it to an IIFE; + // finally it collects everything in the `statics` property object. const collectStatics = specPath => { - const statics = specPath.properties.find(createFindPropFn('statics')); - const staticsObject = - (statics && statics.value && statics.value.properties) || []; + let result = specPath.properties.filter(property => + property.key && STATIC_KEYS[property.key.name] + ); const getDefaultProps = findGetDefaultProps(specPath); if (getDefaultProps) { - staticsObject.push(createDefaultProps(getDefaultProps)); + result.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 statics = specPath.properties.find(createFindPropFn('statics')); + result = result.concat( + (statics && statics.value && statics.value.properties) || [] ); + + return result; }; const collectFunctions = specPath => specPath.properties .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) - .filter(isFunctionExpression); // TODO how about stuff that are not functions? + .filter(isFunctionExpression); const findAutobindNamesFor = (subtree, fnNames, literalOrIdentifier) => { const node = literalOrIdentifier; @@ -237,7 +243,7 @@ module.exports = (file, api, options) => { ).forEach(add) ); - return Object.keys(autobindNames).sort((a, b) => a > b); + return Object.keys(autobindNames); }; // --------------------------------------------------------------------------- @@ -249,7 +255,7 @@ module.exports = (file, api, options) => { fn.value ), fn); - const isInitialStateSimple = getInitialState => + const isInitialStateLiftable = getInitialState => getInitialState ? j(getInitialState) .find(j.MemberExpression, { @@ -277,7 +283,7 @@ module.exports = (file, api, options) => { }) .forEach(path => j(path).replaceWith(j.identifier('props'))); - const inlineGetInitialState = getInitialState => + const liftGetInitialState = getInitialState => getInitialState.value.body.body.map(statement => { if (statement.type === 'ReturnStatement') { return j.expressionStatement( @@ -296,7 +302,7 @@ module.exports = (file, api, options) => { return statement; }); - const createInitialStateValue = value => { + const pickReturnValueOrCreateIIFE = value => { if (hasSingleReturnStatement(value)) { return value.body.body[0].argument; } else { @@ -310,7 +316,7 @@ module.exports = (file, api, options) => { const convertInitialStateToClassProperty = getInitialState => withComments(j.classProperty( j.identifier('state'), - createInitialStateValue(getInitialState.value), + pickReturnValueOrCreateIIFE(getInitialState.value), null, false ), getInitialState); @@ -320,7 +326,7 @@ module.exports = (file, api, options) => { }; const createConstructor = getInitialState => { - updatePropsAccess(getInitialState); // TODO no side-effects? + updatePropsAccess(getInitialState); return [ createMethodDefinition({ @@ -338,7 +344,7 @@ module.exports = (file, api, options) => { ) ), ], - inlineGetInitialState(getInitialState) + liftGetInitialState(getInitialState) ) ) ), @@ -346,13 +352,14 @@ module.exports = (file, api, options) => { ]; }; - const createArrowFunctionExpression = fn => j.arrowFunctionExpression( - fn.params, - fn.body, - false - ); + const createArrowFunctionExpression = fn => + j.arrowFunctionExpression( + fn.params, + fn.body, + false + ); - const createArrowPropertyFromMethod = method => + const createArrowPropertyFromMethod = method => // TODO fix flow annotations withComments(j.classProperty( j.identifier(method.key.name), createArrowFunctionExpression(method.value), @@ -362,10 +369,18 @@ module.exports = (file, api, options) => { // if there's no `getInitialState` or the `getInitialState` function is simple // (i.e., it does not reference `this.props`) then we don't need a constructor. - // we can simply do `state = {...}` as a property initializer. + // we can simply lift `state = {...}` as a property initializer. // otherwise, create a constructor and inline `this.state = ...`. + // + // It creates a class with the following order of properties/methods: + // 1. static properties + // 2. constructor (if necessary) + // 3. new properties (`state = {...};`) + // 4. arrow functions + // 5. other methods const createESClass = ( name, + baseClassName, staticProperties, getInitialState, autobindFunctionNames, @@ -375,7 +390,7 @@ module.exports = (file, api, options) => { let newConstructor = []; const newProperties = []; - if (isInitialStateSimple(getInitialState)) { + if (isInitialStateLiftable(getInitialState)) { if (getInitialState) { newProperties.push(convertInitialStateToClassProperty(getInitialState)); } @@ -395,8 +410,6 @@ module.exports = (file, api, options) => { } } - arrowBindFunctions.sort((a, b) => a.key.name > b.key.name); - return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( @@ -410,7 +423,7 @@ module.exports = (file, api, options) => { ), j.memberExpression( j.identifier('React'), - j.identifier('Component'), + j.identifier(baseClassName), false ) ), {comments}); @@ -438,23 +451,12 @@ module.exports = (file, api, options) => { 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) + pickReturnValueOrCreateIIFE(prop.value) ), prop ); @@ -488,10 +490,15 @@ module.exports = (file, api, options) => { } const staticProperties = createStaticClassProperties(statics); + const baseClassName = + ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) ? + 'PureComponent' : + 'Component'; path.replaceWith( createESClass( name, + baseClassName, staticProperties, getInitialState, autobindFunctionNames, @@ -506,7 +513,7 @@ module.exports = (file, api, options) => { ) { const apply = (path, type) => path - .filter(hasMixins) + .filter(hasInconvertibleMixins) .filter(callsDeprecatedAPIs) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index 0093778c..83248702 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -158,6 +158,35 @@ module.exports = function(j) { return spec && spec.properties.some(isMixinProperty); }; + const containSameElements = (ls1, ls2) => { + if (ls1.length !== ls2.length) { + return false; + } + + return ( + ls1.reduce((res, x) => res && ls2.indexOf(x) !== -1, true) && + ls2.reduce((res, x) => res && ls1.indexOf(x) !== -1, true) + ); + }; + + const isSpecificMixinsProperty = (property, mixinIdentifierNames) => { + const key = property.key; + const value = property.value; + + return ( + key.name === 'mixins' && + value.type === 'ArrayExpression' && + Array.isArray(value.elements) && + value.elements.every(elem => elem.type === 'Identifier') && + containSameElements(value.elements.map(elem => elem.name), mixinIdentifierNames) + ); + }; + + const hasSpecificMixins = (classPath, mixinIdentifierNames) => { + const spec = getReactCreateClassSpec(classPath); + return spec && spec.properties.some(prop => isSpecificMixinsProperty(prop, mixinIdentifierNames)); + }; + // --------------------------------------------------------------------------- // Others const getReactCreateClassSpec = classPath => { @@ -197,6 +226,7 @@ module.exports = function(j) { getReactCreateClassSpec, getClassExtendReactSpec, hasMixins, + hasSpecificMixins, hasModule, hasReact, isMixinProperty, From 93e59311a15bcb6317e2216ef3da5baec7232045 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 13 Jun 2016 16:07:54 -0700 Subject: [PATCH 06/64] added a test for ES6 export --- README.md | 28 ++++--------------- ...ort-default-property-initializer.output.js | 12 +++----- .../__tests__/property-initializer-test.js | 1 + 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 6c73d017..90371218 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,7 @@ The constructor logic is as follows: * Pulls out all statics defined on `statics` plus the few special cased statics like `propTypes`, `childContextTypes`, `contextTypes`, and `displayName` and transforms them to `static` properties at the very top. - like `static displayName = 'Counter';` - * TODO do we bind stuff in the `static` object? - * Takes `getDefaultProps` and inlines it as a static `defaultProps`. + * Takes `getDefaultProps` and inlines it as `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 @@ -168,34 +166,20 @@ The constructor logic is as follows: 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. + * If there exists references to `this.props` in `getInitialState` then it creates + a constructor and converts `getInitialState` to an assignment to `this.state`; + Otherwise it lifts `getInitialState` to a property initializer (`state = ...;`). * Transforms class methods to arrow functions as class property initializers (i.e., to bind them) 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 (lifecycle methods) and ignores functions that are being called directly (unless it is both called directly and passed around to - somewhere else) - * TODO how do we handle `getInitialState`? - * If we reference `this.props` in `getInitialState` then it - has to be in the constructor - * Otherwise it's simple and just make it a property initializer - * TODO [???] When `--no-super-class` is passed it only optionally extends + somewhere else). + * TODO 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 diff --git a/transforms/__testfixtures__/export-default-property-initializer.output.js b/transforms/__testfixtures__/export-default-property-initializer.output.js index 21bc58c9..dec5942a 100644 --- a/transforms/__testfixtures__/export-default-property-initializer.output.js +++ b/transforms/__testfixtures__/export-default-property-initializer.output.js @@ -5,18 +5,14 @@ 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, }; + state = { + foo: 'bar', + }; + render() { return
; } diff --git a/transforms/__tests__/property-initializer-test.js b/transforms/__tests__/property-initializer-test.js index 6972b4ee..c4092fb7 100644 --- a/transforms/__tests__/property-initializer-test.js +++ b/transforms/__tests__/property-initializer-test.js @@ -13,3 +13,4 @@ const defineTest = require('jscodeshift/dist/testUtils').defineTest; defineTest(__dirname, 'property-initializer'); defineTest(__dirname, 'property-initializer', null, 'property-initializer-2'); +defineTest(__dirname, 'property-initializer', null, 'export-default-property-initializer'); From a15133682dc2c6b6df3ea4f0ddd71fa4b041dfc8 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 13 Jun 2016 16:51:56 -0700 Subject: [PATCH 07/64] renamed liftGetInitialState --- transforms/property-initializer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transforms/property-initializer.js b/transforms/property-initializer.js index eda3cd4d..a80bcff7 100644 --- a/transforms/property-initializer.js +++ b/transforms/property-initializer.js @@ -283,7 +283,7 @@ module.exports = (file, api, options) => { }) .forEach(path => j(path).replaceWith(j.identifier('props'))); - const liftGetInitialState = getInitialState => + const inlineGetInitialState = getInitialState => getInitialState.value.body.body.map(statement => { if (statement.type === 'ReturnStatement') { return j.expressionStatement( @@ -344,7 +344,7 @@ module.exports = (file, api, options) => { ) ), ], - liftGetInitialState(getInitialState) + inlineGetInitialState(getInitialState) ) ) ), From 20aafc348e53503add11ee3d506535c853b39645 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 13 Jun 2016 17:00:27 -0700 Subject: [PATCH 08/64] changed to update the existing one instead --- README.md | 46 -- ...alizer-2.input.js => class-test2.input.js} | 0 transforms/__testfixtures__/class-test2.js | 20 - .../__testfixtures__/class-test2.output.js | 68 ++- transforms/__testfixtures__/class.input.js | 16 +- transforms/__testfixtures__/class.output.js | 94 ++- .../export-default-class.output.js | 12 +- .../property-initializer-2.output.js | 78 --- .../property-initializer.input.js | 133 ----- .../property-initializer.output.js | 136 ----- transforms/__tests__/class-test.js | 1 + .../__tests__/property-initializer-test.js | 16 - transforms/class.js | 260 +++++---- transforms/property-initializer.js | 537 ------------------ 14 files changed, 254 insertions(+), 1163 deletions(-) rename transforms/__testfixtures__/{property-initializer-2.input.js => class-test2.input.js} (100%) delete mode 100644 transforms/__testfixtures__/class-test2.js delete mode 100644 transforms/__testfixtures__/property-initializer-2.output.js delete mode 100644 transforms/__testfixtures__/property-initializer.input.js delete mode 100644 transforms/__testfixtures__/property-initializer.output.js delete mode 100644 transforms/__tests__/property-initializer-test.js delete mode 100644 transforms/property-initializer.js diff --git a/README.md b/README.md index 90371218..81030d1d 100644 --- a/README.md +++ b/README.md @@ -100,52 +100,6 @@ guide](https://github.com/airbnb/javascript/blob/7684892951ef663e1c4e62ad57d662e jscodeshift -t react-codemod/transforms/sort-comp.js ``` -### 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. - ### Explanation of the new ES2015 class transform with property initializers * Ignore components with calls to deprecated APIs. This is very defensive, if diff --git a/transforms/__testfixtures__/property-initializer-2.input.js b/transforms/__testfixtures__/class-test2.input.js similarity index 100% rename from transforms/__testfixtures__/property-initializer-2.input.js rename to transforms/__testfixtures__/class-test2.input.js diff --git a/transforms/__testfixtures__/class-test2.js b/transforms/__testfixtures__/class-test2.js deleted file mode 100644 index b0f04327..00000000 --- a/transforms/__testfixtures__/class-test2.js +++ /dev/null @@ -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
; - }, -}); diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index 68803e41..dfd5855e 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -1,22 +1,78 @@ 'use strict'; var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); +var FooBarMixin = require('FooBarMixin'); + +class ComponentWithNonSimpleInitialState extends React.Component { + static iDontKnowWhyYouNeedThis = true; // but comment it + static foo = 'bar'; -// Comment -module.exports = class extends React.Component { constructor(props, context) { super(props, context); this.state = { - foo: 'bar', + counter: props.initialNumber + 1, }; } + render() { + return ( +
{this.state.counter}
+ ); + } +} + +// Comment +module.exports = class extends React.Component { + static propTypes = { + foo: React.PropTypes.bool, + }; + + static defaultProps = { + foo: 12, + }; + + state = function() { // non-simple + var data = 'bar'; + return { + bar: data, + }; + }(); + render() { return
; } }; -module.exports.propTypes = { - foo: React.PropTypes.bool, -}; +class ComponentWithOnlyPureRenderMixin extends React.PureComponent { + constructor(props, context) { + super(props, context); + + this.state = { + counter: props.initialNumber + 1, + }; + } + + render() { + return ( +
{this.state.counter}
+ ); + } +} + +var ComponentWithInconvertibleMixins = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 924ce77f..813f82d5 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -16,7 +16,7 @@ var MyComponent = React.createClass({ }; }, - foo: function() { + foo: function(): void { this.setState({heyoo: 24}); }, }); @@ -26,7 +26,7 @@ var MyComponent2 = React.createClass({ getDefaultProps: function() { return {a: 1}; }, - foo: function() { + foo: function() { // flow annotations dont work for now pass(this.foo); this.forceUpdate(); }, @@ -35,7 +35,7 @@ var MyComponent2 = React.createClass({ var MyComponent3 = React.createClass({ statics: { someThing: 10, - foo: function() {}, + funcThatDoesNothing: function(): void {}, }, propTypes: { highlightEntities: React.PropTypes.bool, @@ -47,7 +47,7 @@ var MyComponent3 = React.createClass({ }, getDefaultProps: function() { - foo(); + unboundFunc(); return { linkifyEntities: true, highlightEntities: false, @@ -61,11 +61,11 @@ var MyComponent3 = React.createClass({ }; }, - _renderText: function(text) { + _renderText: function(text: string) { // TODO no return type yet return ; }, - _renderImageRange: function(text, range) { + _renderImageRange: function(text: string, range) { // TODO no return type yet var image = range.image; if (image) { return ( @@ -79,10 +79,10 @@ var MyComponent3 = React.createClass({ }, autobindMe: function() {}, - dontAutobindMe: function() {}, + dontAutobindMe: function(): number { return 12; }, // Function comment - _renderRange: function(text, range) { + _renderRange: function(text: string, range, bla: Promise) { var self = this; self.dontAutobindMe(); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index fedb3bb6..2566a19f 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -18,32 +18,44 @@ class MyComponent extends React.Component { }; } - foo() { + foo(): void { this.setState({heyoo: 24}); } } // Class comment class MyComponent2 extends React.Component { - constructor(props, context) { - super(props, context); - this.foo = this.foo.bind(this); - } + static defaultProps = {a: 1}; - foo() { + foo = () => { // flow annotations dont work for now pass(this.foo); this.forceUpdate(); - } + }; } -MyComponent2.defaultProps = {a: 1}; - class MyComponent3 extends React.Component { + static propTypes = { + highlightEntities: React.PropTypes.bool, + linkifyEntities: React.PropTypes.bool, + text: React.PropTypes.shape({ + text: React.PropTypes.string, + ranges: React.PropTypes.array, + }).isRequired, + }; + + static defaultProps = function() { + unboundFunc(); + return { + linkifyEntities: true, + highlightEntities: false, + }; + }(); + + static someThing = 10; + static funcThatDoesNothing = function(): void {}; + 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 = { @@ -51,28 +63,14 @@ class MyComponent3 extends React.Component { }; } - _renderText(text) { + _renderText = (text: string) => { // TODO no return type yet return ; - } - - _renderImageRange(text, range) { - var image = range.image; - if (image) { - return ( - - ); - } - } + }; - autobindMe() {} - dontAutobindMe() {} + autobindMe = () => {}; // Function comment - _renderRange(text, range) { + _renderRange = (text: string, range, bla: Promise) => { var self = this; self.dontAutobindMe(); @@ -95,8 +93,23 @@ class MyComponent3 extends React.Component { } return text; + }; + + _renderImageRange(text: string, range) { // TODO no return type yet + var image = range.image; + if (image) { + return ( + + ); + } } + dontAutobindMe(): number { return 12; } + /* This is a comment */ render() { var content = this.props.text; @@ -111,27 +124,6 @@ class MyComponent3 extends React.Component { } } -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() {}, diff --git a/transforms/__testfixtures__/export-default-class.output.js b/transforms/__testfixtures__/export-default-class.output.js index 21bc58c9..dec5942a 100644 --- a/transforms/__testfixtures__/export-default-class.output.js +++ b/transforms/__testfixtures__/export-default-class.output.js @@ -5,18 +5,14 @@ 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, }; + state = { + foo: 'bar', + }; + render() { return
; } diff --git a/transforms/__testfixtures__/property-initializer-2.output.js b/transforms/__testfixtures__/property-initializer-2.output.js deleted file mode 100644 index dfd5855e..00000000 --- a/transforms/__testfixtures__/property-initializer-2.output.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -var React = require('React'); -var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); -var FooBarMixin = require('FooBarMixin'); - -class ComponentWithNonSimpleInitialState extends React.Component { - static iDontKnowWhyYouNeedThis = true; // but comment it - static foo = 'bar'; - - constructor(props, context) { - super(props, context); - - this.state = { - counter: props.initialNumber + 1, - }; - } - - render() { - return ( -
{this.state.counter}
- ); - } -} - -// Comment -module.exports = class extends React.Component { - static propTypes = { - foo: React.PropTypes.bool, - }; - - static defaultProps = { - foo: 12, - }; - - state = function() { // non-simple - var data = 'bar'; - return { - bar: data, - }; - }(); - - render() { - return
; - } -}; - -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props, context) { - super(props, context); - - this.state = { - counter: props.initialNumber + 1, - }; - } - - render() { - return ( -
{this.state.counter}
- ); - } -} - -var ComponentWithInconvertibleMixins = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); diff --git a/transforms/__testfixtures__/property-initializer.input.js b/transforms/__testfixtures__/property-initializer.input.js deleted file mode 100644 index 813f82d5..00000000 --- a/transforms/__testfixtures__/property-initializer.input.js +++ /dev/null @@ -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(): void { - this.setState({heyoo: 24}); - }, -}); - -// Class comment -var MyComponent2 = React.createClass({ - getDefaultProps: function() { - return {a: 1}; - }, - foo: function() { // flow annotations dont work for now - pass(this.foo); - this.forceUpdate(); - }, -}); - -var MyComponent3 = React.createClass({ - statics: { - someThing: 10, - funcThatDoesNothing: function(): void {}, - }, - propTypes: { - highlightEntities: React.PropTypes.bool, - linkifyEntities: React.PropTypes.bool, - text: React.PropTypes.shape({ - text: React.PropTypes.string, - ranges: React.PropTypes.array, - }).isRequired, - }, - - getDefaultProps: function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; - }, - - getInitialState: function() { - this.props.foo(); - return { - heyoo: 23, - }; - }, - - _renderText: function(text: string) { // TODO no return type yet - return ; - }, - - _renderImageRange: function(text: string, range) { // TODO no return type yet - var image = range.image; - if (image) { - return ( - - ); - } - }, - - autobindMe: function() {}, - dontAutobindMe: function(): number { return 12; }, - - // Function comment - _renderRange: function(text: string, range, bla: Promise) { - 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 = - - {text} - ; - } else { - text = {text}; - } - - return text; - }, - - /* This is a comment */ - render: function() { - var content = this.props.text; - return ( - - ); - }, -}); - -var MyComponent4 = React.createClass({ - foo: callMeMaybe(), - render: function() {}, -}); - -module.exports = Relay.createContainer(MyComponent, { - queries: { - me: Relay.graphql`this is not graphql`, - }, -}); diff --git a/transforms/__testfixtures__/property-initializer.output.js b/transforms/__testfixtures__/property-initializer.output.js deleted file mode 100644 index 2566a19f..00000000 --- a/transforms/__testfixtures__/property-initializer.output.js +++ /dev/null @@ -1,136 +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(): void { - this.setState({heyoo: 24}); - } -} - -// Class comment -class MyComponent2 extends React.Component { - static defaultProps = {a: 1}; - - foo = () => { // flow annotations dont work for now - pass(this.foo); - this.forceUpdate(); - }; -} - -class MyComponent3 extends React.Component { - static propTypes = { - highlightEntities: React.PropTypes.bool, - linkifyEntities: React.PropTypes.bool, - text: React.PropTypes.shape({ - text: React.PropTypes.string, - ranges: React.PropTypes.array, - }).isRequired, - }; - - static defaultProps = function() { - unboundFunc(); - return { - linkifyEntities: true, - highlightEntities: false, - }; - }(); - - static someThing = 10; - static funcThatDoesNothing = function(): void {}; - - constructor(props, context) { - super(props, context); - props.foo(); - - this.state = { - heyoo: 23, - }; - } - - _renderText = (text: string) => { // TODO no return type yet - return ; - }; - - autobindMe = () => {}; - - // Function comment - _renderRange = (text: string, range, bla: Promise) => { - 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 = - - {text} - ; - } else { - text = {text}; - } - - return text; - }; - - _renderImageRange(text: string, range) { // TODO no return type yet - var image = range.image; - if (image) { - return ( - - ); - } - } - - dontAutobindMe(): number { return 12; } - - /* This is a comment */ - render() { - var content = this.props.text; - return ( - - ); - } -} - -var MyComponent4 = React.createClass({ - foo: callMeMaybe(), - render: function() {}, -}); - -module.exports = Relay.createContainer(MyComponent, { - queries: { - me: Relay.graphql`this is not graphql`, - }, -}); diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 02355b38..9d1763ac 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -12,4 +12,5 @@ const defineTest = require('jscodeshift/dist/testUtils').defineTest; defineTest(__dirname, 'class'); +defineTest(__dirname, 'class', null, 'class-test2'); defineTest(__dirname, 'class', null, 'export-default-class'); diff --git a/transforms/__tests__/property-initializer-test.js b/transforms/__tests__/property-initializer-test.js deleted file mode 100644 index c4092fb7..00000000 --- a/transforms/__tests__/property-initializer-test.js +++ /dev/null @@ -1,16 +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'; - -const defineTest = require('jscodeshift/dist/testUtils').defineTest; -defineTest(__dirname, 'property-initializer'); -defineTest(__dirname, 'property-initializer', null, 'property-initializer-2'); -defineTest(__dirname, 'property-initializer', null, 'export-default-property-initializer'); diff --git a/transforms/class.js b/transforms/class.js index 4ddff794..a80bcff7 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -54,6 +54,8 @@ module.exports = (file, api, options) => { propTypes: true, }; + const MIXIN_KEY = 'mixins'; + // --------------------------------------------------------------------------- // Checks if the module uses mixins or accesses deprecated APIs. const checkDeprecatedAPICalls = classPath => @@ -86,7 +88,8 @@ module.exports = (file, api, options) => { STATIC_KEY != prop.key.name && !filterDefaultPropsField(prop) && !filterGetInitialStateField(prop) && - !isFunctionExpression(prop) + !isFunctionExpression(prop) && + MIXIN_KEY != prop.key.name ) )); @@ -105,11 +108,14 @@ module.exports = (file, api, options) => { return !invalidProperties.length; }; - const hasMixins = classPath => { - if (ReactUtils.hasMixins(classPath)) { + const hasInconvertibleMixins = classPath => { + if ( + ReactUtils.hasMixins(classPath) && + !ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) + ) { console.log( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + - 'was skipped because of mixins.' + 'was skipped because of inconvertible mixins.' ); return false; } @@ -150,22 +156,25 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); + // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` first; + // then simplifies `getDefaultProps` or converts it to an IIFE; + // finally it collects everything in the `statics` property object. const collectStatics = specPath => { - const statics = specPath.properties.find(createFindPropFn('statics')); - const staticsObject = - (statics && statics.value && statics.value.properties) || []; + let result = specPath.properties.filter(property => + property.key && STATIC_KEYS[property.key.name] + ); const getDefaultProps = findGetDefaultProps(specPath); if (getDefaultProps) { - staticsObject.push(createDefaultProps(getDefaultProps)); + result.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 statics = specPath.properties.find(createFindPropFn('statics')); + result = result.concat( + (statics && statics.value && statics.value.properties) || [] ); + + return result; }; const collectFunctions = specPath => specPath.properties @@ -234,7 +243,7 @@ module.exports = (file, api, options) => { ).forEach(add) ); - return Object.keys(autobindNames).sort(); + return Object.keys(autobindNames); }; // --------------------------------------------------------------------------- @@ -246,31 +255,7 @@ module.exports = (file, api, options) => { 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 updatePropsAccess = getInitialState => + const isInitialStateLiftable = getInitialState => getInitialState ? j(getInitialState) .find(j.MemberExpression, { @@ -282,16 +267,24 @@ module.exports = (file, api, options) => { name: 'props', }, }) - .forEach(path => j(path).replaceWith(j.identifier('props'))) - .size() > 0 : - false; + .size() === 0 : + true; - const inlineGetInitialState = getInitialState => { - if (!getInitialState) { - return []; - } + const updatePropsAccess = getInitialState => + j(getInitialState) + .find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'props', + }, + }) + .forEach(path => j(path).replaceWith(j.identifier('props'))); - return getInitialState.value.body.body.map(statement => { + const inlineGetInitialState = getInitialState => + getInitialState.value.body.body.map(statement => { if (statement.type === 'ReturnStatement') { return j.expressionStatement( j.assignmentExpression( @@ -308,27 +301,39 @@ module.exports = (file, api, options) => { return statement; }); + + const pickReturnValueOrCreateIIFE = value => { + if (hasSingleReturnStatement(value)) { + return value.body.body[0].argument; + } else { + return j.callExpression( + value, + [] + ); + } }; - const createConstructorArgs = (hasPropsAccess) => { + const convertInitialStateToClassProperty = getInitialState => + withComments(j.classProperty( + j.identifier('state'), + pickReturnValueOrCreateIIFE(getInitialState.value), + null, + false + ), getInitialState); + + const createConstructorArgs = () => { return [j.identifier('props'), j.identifier('context')]; }; - const createConstructor = ( - getInitialState, - autobindFunctions - ) => { - if (!getInitialState && !autobindFunctions.length) { - return []; - } + const createConstructor = getInitialState => { + updatePropsAccess(getInitialState); - const hasPropsAccess = updatePropsAccess(getInitialState); return [ createMethodDefinition({ key: j.identifier('constructor'), value: j.functionExpression( null, - createConstructorArgs(hasPropsAccess), + createConstructorArgs(), j.blockStatement( [].concat( [ @@ -339,7 +344,6 @@ module.exports = (file, api, options) => { ) ), ], - autobindFunctions.map(createBindAssignment), inlineGetInitialState(getInitialState) ) ) @@ -348,46 +352,82 @@ module.exports = (file, api, options) => { ]; }; + const createArrowFunctionExpression = fn => + j.arrowFunctionExpression( + fn.params, + fn.body, + false + ); + + const createArrowPropertyFromMethod = method => // TODO fix flow annotations + withComments(j.classProperty( + j.identifier(method.key.name), + createArrowFunctionExpression(method.value), + null, + false + ), method); + + // if there's no `getInitialState` or the `getInitialState` function is simple + // (i.e., it does not reference `this.props`) then we don't need a constructor. + // we can simply lift `state = {...}` as a property initializer. + // otherwise, create a constructor and inline `this.state = ...`. + // + // It creates a class with the following order of properties/methods: + // 1. static properties + // 2. constructor (if necessary) + // 3. new properties (`state = {...};`) + // 4. arrow functions + // 5. other methods const createESClass = ( name, - properties, + baseClassName, + staticProperties, getInitialState, - autobindFunctions, + autobindFunctionNames, + methods, comments - ) => - withComments(j.classDeclaration( + ) => { + let newConstructor = []; + const newProperties = []; + + if (isInitialStateLiftable(getInitialState)) { + if (getInitialState) { + newProperties.push(convertInitialStateToClassProperty(getInitialState)); + } + } else { + newConstructor = createConstructor(getInitialState); + } + + const arrowBindFunctions = []; + const newMethods = []; + + for (let i = 0; i < methods.length; i++) { + const method = methods[i]; + if (autobindFunctionNames.indexOf(method.key.name) !== -1) { + arrowBindFunctions.push(method); + } else { + newMethods.push(method); + } + } + + return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( [].concat( - createConstructor( - getInitialState, - autobindFunctions - ), - properties + staticProperties, + newConstructor, + newProperties, + arrowBindFunctions.map(createArrowPropertyFromMethod), + newMethods ) ), j.memberExpression( j.identifier('React'), - j.identifier('Component'), + j.identifier(baseClassName), false ) ), {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( @@ -411,23 +451,12 @@ module.exports = (file, api, options) => { 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) + pickReturnValueOrCreateIIFE(prop.value) ), prop ); @@ -443,13 +472,6 @@ module.exports = (file, api, options) => { return null; }; - const createModuleExportsMemberExpression = () => - j.memberExpression( - j.identifier('module'), - j.identifier('exports'), - false - ); - const updateToClass = (classPath, type) => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const name = ReactUtils.getComponentName(classPath); @@ -457,12 +479,9 @@ module.exports = (file, api, options) => { const functions = collectFunctions(specPath); const comments = getComments(classPath); - const autobindFunctions = collectAutoBindFunctions(functions, classPath); + const autobindFunctionNames = 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); @@ -470,30 +489,23 @@ module.exports = (file, api, options) => { path = j(classPath).closest(j.VariableDeclaration); } - const properties = - (type == 'exportDefault') ? createStaticClassProperties(statics) : []; + const staticProperties = createStaticClassProperties(statics); + const baseClassName = + ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) ? + 'PureComponent' : + 'Component'; path.replaceWith( createESClass( name, - properties.concat(functions.map(createMethodDefinition)), + baseClassName, + staticProperties, getInitialState, - autobindFunctions, + autobindFunctionNames, + functions.map(createMethodDefinition), comments ) ); - - 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 ( @@ -501,7 +513,7 @@ module.exports = (file, api, options) => { ) { const apply = (path, type) => path - .filter(hasMixins) + .filter(hasInconvertibleMixins) .filter(callsDeprecatedAPIs) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); diff --git a/transforms/property-initializer.js b/transforms/property-initializer.js deleted file mode 100644 index a80bcff7..00000000 --- a/transforms/property-initializer.js +++ /dev/null @@ -1,537 +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 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, - }; - - const MIXIN_KEY = 'mixins'; - - // --------------------------------------------------------------------------- - // 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) && - MIXIN_KEY != prop.key.name - ) - )); - - 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 hasInconvertibleMixins = classPath => { - if ( - ReactUtils.hasMixins(classPath) && - !ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) - ) { - console.log( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + - 'was skipped because of inconvertible 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)); - - 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' - ); - - // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` first; - // then simplifies `getDefaultProps` or converts it to an IIFE; - // finally it collects everything in the `statics` property object. - const collectStatics = specPath => { - let result = specPath.properties.filter(property => - property.key && STATIC_KEYS[property.key.name] - ); - - const getDefaultProps = findGetDefaultProps(specPath); - if (getDefaultProps) { - result.push(createDefaultProps(getDefaultProps)); - } - - const statics = specPath.properties.find(createFindPropFn('statics')); - result = result.concat( - (statics && statics.value && statics.value.properties) || [] - ); - - return result; - }; - - 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.` - findAutobindNamesFor(classPath, fnNames, j.thisExpression()).forEach(add); - - // Find `self.` 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); - }; - - // --------------------------------------------------------------------------- - // Boom! - const createMethodDefinition = fn => - withComments(j.methodDefinition( - 'method', - fn.key, - fn.value - ), fn); - - const isInitialStateLiftable = getInitialState => - getInitialState ? - j(getInitialState) - .find(j.MemberExpression, { - object: { - type: 'ThisExpression', - }, - property: { - type: 'Identifier', - name: 'props', - }, - }) - .size() === 0 : - true; - - const updatePropsAccess = getInitialState => - j(getInitialState) - .find(j.MemberExpression, { - object: { - type: 'ThisExpression', - }, - property: { - type: 'Identifier', - name: 'props', - }, - }) - .forEach(path => j(path).replaceWith(j.identifier('props'))); - - const inlineGetInitialState = getInitialState => - 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 pickReturnValueOrCreateIIFE = value => { - if (hasSingleReturnStatement(value)) { - return value.body.body[0].argument; - } else { - return j.callExpression( - value, - [] - ); - } - }; - - const convertInitialStateToClassProperty = getInitialState => - withComments(j.classProperty( - j.identifier('state'), - pickReturnValueOrCreateIIFE(getInitialState.value), - null, - false - ), getInitialState); - - const createConstructorArgs = () => { - return [j.identifier('props'), j.identifier('context')]; - }; - - const createConstructor = getInitialState => { - updatePropsAccess(getInitialState); - - return [ - createMethodDefinition({ - key: j.identifier('constructor'), - value: j.functionExpression( - null, - createConstructorArgs(), - j.blockStatement( - [].concat( - [ - j.expressionStatement( - j.callExpression( - j.identifier('super'), - [j.identifier('props'), j.identifier('context')] - ) - ), - ], - inlineGetInitialState(getInitialState) - ) - ) - ), - }), - ]; - }; - - const createArrowFunctionExpression = fn => - j.arrowFunctionExpression( - fn.params, - fn.body, - false - ); - - const createArrowPropertyFromMethod = method => // TODO fix flow annotations - withComments(j.classProperty( - j.identifier(method.key.name), - createArrowFunctionExpression(method.value), - null, - false - ), method); - - // if there's no `getInitialState` or the `getInitialState` function is simple - // (i.e., it does not reference `this.props`) then we don't need a constructor. - // we can simply lift `state = {...}` as a property initializer. - // otherwise, create a constructor and inline `this.state = ...`. - // - // It creates a class with the following order of properties/methods: - // 1. static properties - // 2. constructor (if necessary) - // 3. new properties (`state = {...};`) - // 4. arrow functions - // 5. other methods - const createESClass = ( - name, - baseClassName, - staticProperties, - getInitialState, - autobindFunctionNames, - methods, - comments - ) => { - let newConstructor = []; - const newProperties = []; - - if (isInitialStateLiftable(getInitialState)) { - if (getInitialState) { - newProperties.push(convertInitialStateToClassProperty(getInitialState)); - } - } else { - newConstructor = createConstructor(getInitialState); - } - - const arrowBindFunctions = []; - const newMethods = []; - - for (let i = 0; i < methods.length; i++) { - const method = methods[i]; - if (autobindFunctionNames.indexOf(method.key.name) !== -1) { - arrowBindFunctions.push(method); - } else { - newMethods.push(method); - } - } - - return withComments(j.classDeclaration( - name ? j.identifier(name) : null, - j.classBody( - [].concat( - staticProperties, - newConstructor, - newProperties, - arrowBindFunctions.map(createArrowPropertyFromMethod), - newMethods - ) - ), - j.memberExpression( - j.identifier('React'), - j.identifier(baseClassName), - false - ) - ), {comments}); - }; - - 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 createDefaultProps = prop => - withComments( - j.property( - 'init', - j.identifier(DEFAULT_PROPS_KEY), - pickReturnValueOrCreateIIFE(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 updateToClass = (classPath, type) => { - const specPath = ReactUtils.getReactCreateClassSpec(classPath); - const name = ReactUtils.getComponentName(classPath); - const statics = collectStatics(specPath); - const functions = collectFunctions(specPath); - const comments = getComments(classPath); - - const autobindFunctionNames = collectAutoBindFunctions(functions, classPath); - const getInitialState = findGetInitialState(specPath); - - var path; - if (type == 'moduleExports' || type == 'exportDefault') { - path = ReactUtils.findReactCreateClassCallExpression(classPath); - } else { - path = j(classPath).closest(j.VariableDeclaration); - } - - const staticProperties = createStaticClassProperties(statics); - const baseClassName = - ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) ? - 'PureComponent' : - 'Component'; - - path.replaceWith( - createESClass( - name, - baseClassName, - staticProperties, - getInitialState, - autobindFunctionNames, - functions.map(createMethodDefinition), - comments - ) - ); - }; - - if ( - options['explicit-require'] === false || ReactUtils.hasReact(root) - ) { - const apply = (path, type) => - path - .filter(hasInconvertibleMixins) - .filter(callsDeprecatedAPIs) - .filter(canConvertToClass) - .forEach(classPath => updateToClass(classPath, 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; -}; From c2bda7181bea452e5f00fbd9c82318aa0e85ec36 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 14 Jun 2016 14:20:17 -0700 Subject: [PATCH 09/64] fixed arrow function return type --- transforms/__testfixtures__/class.input.js | 11 ++++++----- transforms/__testfixtures__/class.output.js | 9 +++++---- transforms/class.js | 11 ++++++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 813f82d5..0b395241 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -23,10 +23,10 @@ var MyComponent = React.createClass({ // Class comment var MyComponent2 = React.createClass({ - getDefaultProps: function() { + getDefaultProps: function(): Object { return {a: 1}; }, - foo: function() { // flow annotations dont work for now + foo: function(): void { pass(this.foo); this.forceUpdate(); }, @@ -61,11 +61,12 @@ var MyComponent3 = React.createClass({ }; }, - _renderText: function(text: string) { // TODO no return type yet + // comment here + _renderText: function(text: string): ReactElement { // say something return ; }, - _renderImageRange: function(text: string, range) { // TODO no return type yet + _renderImageRange: function(text: string, range): ReactElement { var image = range.image; if (image) { return ( @@ -82,7 +83,7 @@ var MyComponent3 = React.createClass({ dontAutobindMe: function(): number { return 12; }, // Function comment - _renderRange: function(text: string, range, bla: Promise) { + _renderRange: function(text: string, range, bla: Promise): ReactElement { var self = this; self.dontAutobindMe(); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 2566a19f..28dbf343 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -27,7 +27,7 @@ class MyComponent extends React.Component { class MyComponent2 extends React.Component { static defaultProps = {a: 1}; - foo = () => { // flow annotations dont work for now + foo = (): void => { pass(this.foo); this.forceUpdate(); }; @@ -63,14 +63,15 @@ class MyComponent3 extends React.Component { }; } - _renderText = (text: string) => { // TODO no return type yet + // comment here + _renderText = (text: string): ReactElement => { // say something return ; }; autobindMe = () => {}; // Function comment - _renderRange = (text: string, range, bla: Promise) => { + _renderRange = (text: string, range, bla: Promise): ReactElement => { var self = this; self.dontAutobindMe(); @@ -95,7 +96,7 @@ class MyComponent3 extends React.Component { return text; }; - _renderImageRange(text: string, range) { // TODO no return type yet + _renderImageRange(text: string, range): ReactElement { var image = range.image; if (image) { return ( diff --git a/transforms/class.js b/transforms/class.js index a80bcff7..fffd84f6 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -352,14 +352,19 @@ module.exports = (file, api, options) => { ]; }; + const copyReturnType = (to, from) => { + to.returnType = from.returnType; + return to; + }; + const createArrowFunctionExpression = fn => - j.arrowFunctionExpression( + copyReturnType(j.arrowFunctionExpression( fn.params, fn.body, false - ); + ), fn); - const createArrowPropertyFromMethod = method => // TODO fix flow annotations + const createArrowPropertyFromMethod = method => withComments(j.classProperty( j.identifier(method.key.name), createArrowFunctionExpression(method.value), From f5f6da037d5cb4c02b4ae2b828b52b839a8bb137 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 14 Jun 2016 14:20:44 -0700 Subject: [PATCH 10/64] temporally added npm shrinkwrap --- npm-shrinkwrap.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 npm-shrinkwrap.json diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 00000000..ad59efe8 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "jscodeshift": { + "version": "0.3.20", + "dependencies": { + "recast": { + "version": "git://github.com/keyanzhang/recast.git#arrow-function-return-type" + } + } + } + } +} From f70dad28312ef876c02fa985ef69b6fc8f240a80 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 15 Jun 2016 14:23:36 -0700 Subject: [PATCH 11/64] changed initial state transformation --- .../__testfixtures__/class-test2.input.js | 2 +- .../__testfixtures__/class-test2.output.js | 9 +++-- transforms/class.js | 33 +++++++++++-------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index 634b85b1..74eff41c 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -35,7 +35,7 @@ module.exports = React.createClass({ }; }, - getInitialState: function() { // non-simple + getInitialState: function() { // non-simple getInitialState var data = 'bar'; return { bar: data, diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index dfd5855e..7215f619 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -33,12 +33,15 @@ module.exports = class extends React.Component { foo: 12, }; - state = function() { // non-simple + constructor(props, context) { + super(props, context); + // non-simple getInitialState var data = 'bar'; - return { + + this.state = { bar: data, }; - }(); + } render() { return
; diff --git a/transforms/class.js b/transforms/class.js index fffd84f6..f0ff1450 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -255,20 +255,25 @@ module.exports = (file, api, options) => { fn.value ), fn); - const isInitialStateLiftable = getInitialState => - getInitialState ? - j(getInitialState) - .find(j.MemberExpression, { - object: { - type: 'ThisExpression', - }, - property: { - type: 'Identifier', - name: 'props', - }, - }) - .size() === 0 : - true; + const isInitialStateLiftable = getInitialState => { + if (!getInitialState || !(getInitialState.value)) { + return true; + } else if (!hasSingleReturnStatement(getInitialState.value)) { + return false; + } + + return j(getInitialState) + .find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'props', + }, + }) + .size() === 0; + }; const updatePropsAccess = getInitialState => j(getInitialState) From c136e9c792e7addb95b4ebb8c6b83ba578b73286 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 16 Jun 2016 14:35:50 -0700 Subject: [PATCH 12/64] added react-addons-pure-render-mixin support --- .../class-pure-mixin1.input.js | 18 ++++ .../class-pure-mixin1.output.js | 17 ++++ .../class-pure-mixin2.input.js | 18 ++++ .../class-pure-mixin2.output.js | 17 ++++ .../class-pure-mixin3.input.js | 0 .../class-pure-mixin3.output.js | 0 .../__testfixtures__/class-test2.input.js | 16 ---- .../__testfixtures__/class-test2.output.js | 16 ---- ...port-default-property-initializer.input.js | 21 ----- ...ort-default-property-initializer.output.js | 19 ---- transforms/__tests__/class-test.js | 9 +- transforms/class.js | 89 ++++++++++++++++--- 12 files changed, 157 insertions(+), 83 deletions(-) create mode 100644 transforms/__testfixtures__/class-pure-mixin1.input.js create mode 100644 transforms/__testfixtures__/class-pure-mixin1.output.js create mode 100644 transforms/__testfixtures__/class-pure-mixin2.input.js create mode 100644 transforms/__testfixtures__/class-pure-mixin2.output.js create mode 100644 transforms/__testfixtures__/class-pure-mixin3.input.js create mode 100644 transforms/__testfixtures__/class-pure-mixin3.output.js delete mode 100644 transforms/__testfixtures__/export-default-property-initializer.input.js delete mode 100644 transforms/__testfixtures__/export-default-property-initializer.output.js diff --git a/transforms/__testfixtures__/class-pure-mixin1.input.js b/transforms/__testfixtures__/class-pure-mixin1.input.js new file mode 100644 index 00000000..89255327 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin1.input.js @@ -0,0 +1,18 @@ +var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); + +var ComponentWithOnlyPureRenderMixin = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-pure-mixin1.output.js b/transforms/__testfixtures__/class-pure-mixin1.output.js new file mode 100644 index 00000000..c85e4105 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin1.output.js @@ -0,0 +1,17 @@ +var React = require('React'); + +class ComponentWithOnlyPureRenderMixin extends React.PureComponent { + constructor(props, context) { + super(props, context); + + this.state = { + counter: props.initialNumber + 1, + }; + } + + render() { + return ( +
{this.state.counter}
+ ); + } +} diff --git a/transforms/__testfixtures__/class-pure-mixin2.input.js b/transforms/__testfixtures__/class-pure-mixin2.input.js new file mode 100644 index 00000000..cc920621 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin2.input.js @@ -0,0 +1,18 @@ +import React from 'React'; +import WhateverYouCallIt from 'react-addons-pure-render-mixin'; + +var ComponentWithOnlyPureRenderMixin = React.createClass({ + mixins: [WhateverYouCallIt], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-pure-mixin2.output.js b/transforms/__testfixtures__/class-pure-mixin2.output.js new file mode 100644 index 00000000..dfaf9dfa --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin2.output.js @@ -0,0 +1,17 @@ +import React from 'React'; + +class ComponentWithOnlyPureRenderMixin extends React.PureComponent { + constructor(props, context) { + super(props, context); + + this.state = { + counter: props.initialNumber + 1, + }; + } + + render() { + return ( +
{this.state.counter}
+ ); + } +} diff --git a/transforms/__testfixtures__/class-pure-mixin3.input.js b/transforms/__testfixtures__/class-pure-mixin3.input.js new file mode 100644 index 00000000..e69de29b diff --git a/transforms/__testfixtures__/class-pure-mixin3.output.js b/transforms/__testfixtures__/class-pure-mixin3.output.js new file mode 100644 index 00000000..e69de29b diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index 74eff41c..04a1a247 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -47,22 +47,6 @@ module.exports = React.createClass({ }, }); -var ComponentWithOnlyPureRenderMixin = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - counter: this.props.initialNumber + 1, - }; - }, - - render: function() { - return ( -
{this.state.counter}
- ); - }, -}); - var ComponentWithInconvertibleMixins = React.createClass({ mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index 7215f619..f9c96fd8 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -48,22 +48,6 @@ module.exports = class extends React.Component { } }; -class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props, context) { - super(props, context); - - this.state = { - counter: props.initialNumber + 1, - }; - } - - render() { - return ( -
{this.state.counter}
- ); - } -} - var ComponentWithInconvertibleMixins = React.createClass({ mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], diff --git a/transforms/__testfixtures__/export-default-property-initializer.input.js b/transforms/__testfixtures__/export-default-property-initializer.input.js deleted file mode 100644 index 148843b1..00000000 --- a/transforms/__testfixtures__/export-default-property-initializer.input.js +++ /dev/null @@ -1,21 +0,0 @@ -/*eslint-disable no-extra-semi*/ - -'use strict'; - -import React from 'React'; - -export default React.createClass({ - getInitialState: function() { - return { - foo: 'bar', - }; - }, - - propTypes: { - foo: React.PropTypes.string, - }, - - render: function() { - return
; - }, -}); diff --git a/transforms/__testfixtures__/export-default-property-initializer.output.js b/transforms/__testfixtures__/export-default-property-initializer.output.js deleted file mode 100644 index dec5942a..00000000 --- a/transforms/__testfixtures__/export-default-property-initializer.output.js +++ /dev/null @@ -1,19 +0,0 @@ -/*eslint-disable no-extra-semi*/ - -'use strict'; - -import React from 'React'; - -export default class extends React.Component { - static propTypes = { - foo: React.PropTypes.string, - }; - - state = { - foo: 'bar', - }; - - render() { - return
; - } -}; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 9d1763ac..c3233662 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -11,6 +11,13 @@ 'use strict'; const defineTest = require('jscodeshift/dist/testUtils').defineTest; + +const pureMixinAlternativeOption = { + 'mixin-module-name': 'ReactComponentWithPureRenderMixin', +}; + defineTest(__dirname, 'class'); -defineTest(__dirname, 'class', null, 'class-test2'); +defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); defineTest(__dirname, 'class', null, 'export-default-class'); +defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); +defineTest(__dirname, 'class', null, 'class-pure-mixin2'); diff --git a/transforms/class.js b/transforms/class.js index f0ff1450..32d826b1 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -45,6 +45,9 @@ module.exports = (file, api, options) => { 'setProps', ]; + const PURE_MIXIN_MODULE_NAME = options['mixin-module-name'] || + 'react-addons-pure-render-mixin'; + const STATIC_KEY = 'statics'; const STATIC_KEYS = { @@ -108,15 +111,11 @@ module.exports = (file, api, options) => { return !invalidProperties.length; }; - const hasInconvertibleMixins = classPath => { + const areMixinsConvertible = (mixinIdentifierNames, classPath) => { if ( ReactUtils.hasMixins(classPath) && - !ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) + !ReactUtils.hasSpecificMixins(classPath, mixinIdentifierNames) ) { - console.log( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + - 'was skipped because of inconvertible mixins.' - ); return false; } return true; @@ -246,6 +245,49 @@ module.exports = (file, api, options) => { return Object.keys(autobindNames); }; + const findRequirePathAndBinding = (moduleName) => { + let result = null; + + const requireStatement = root.find(j.VariableDeclarator, { + init: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'require', + }, + arguments: [{ + value: moduleName, + }], + }, + }); + + const importStatement = root.find(j.ImportDeclaration, { + source: { + value: moduleName, + }, + }); + + if (importStatement.size() !== 0) { + importStatement.forEach(path => { + result = { + path, + binding: path.value.specifiers[0].local.name, + }; + }); + } else if (requireStatement.size() !== 0) { + requireStatement.forEach(path => { + result = { + path, + binding: path.value.id.name, + }; + }); + } + + return result; + }; + + const pureRenderMixinPathAndBinding = findRequirePathAndBinding(PURE_MIXIN_MODULE_NAME); + // --------------------------------------------------------------------------- // Boom! const createMethodDefinition = fn => @@ -501,9 +543,10 @@ module.exports = (file, api, options) => { const staticProperties = createStaticClassProperties(statics); const baseClassName = - ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) ? - 'PureComponent' : - 'Component'; + pureRenderMixinPathAndBinding && + ReactUtils.hasSpecificMixins(classPath, [pureRenderMixinPathAndBinding.binding]) ? + 'PureComponent' : + 'Component'; path.replaceWith( createESClass( @@ -521,9 +564,29 @@ module.exports = (file, api, options) => { if ( options['explicit-require'] === false || ReactUtils.hasReact(root) ) { + // no mixins found on the classPath -> true + // pure mixin identifier not found -> (has mixins) -> false + // found pure mixin identifier -> + // class mixins only contain the identifier -> true + // otherwise -> false + const mixinsFilter = (classPath) => { + if (!ReactUtils.hasMixins(classPath)) { + return true; + } else if (pureRenderMixinPathAndBinding) { + if (areMixinsConvertible([pureRenderMixinPathAndBinding.binding], classPath)) { + return true; + } + } + console.log( + file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + 'was skipped because of inconvertible mixins.' + ); + return false; + }; + const apply = (path, type) => path - .filter(hasInconvertibleMixins) + .filter(mixinsFilter) .filter(callsDeprecatedAPIs) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); @@ -538,6 +601,12 @@ module.exports = (file, api, options) => { ) > 0; if (didTransform) { + // prune removed requires + if (pureRenderMixinPathAndBinding) { + // FIXME check the scope before removing stuff + j(pureRenderMixinPathAndBinding.path).remove(); + } + return root.toSource(printOptions); } From 311f4720f21df51d22fa3bb6a95513f0dd0e0005 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 16 Jun 2016 19:18:43 -0700 Subject: [PATCH 13/64] Revert "temporally added npm shrinkwrap" This reverts commit f5f6da037d5cb4c02b4ae2b828b52b839a8bb137. --- npm-shrinkwrap.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 npm-shrinkwrap.json diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json deleted file mode 100644 index ad59efe8..00000000 --- a/npm-shrinkwrap.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dependencies": { - "jscodeshift": { - "version": "0.3.20", - "dependencies": { - "recast": { - "version": "git://github.com/keyanzhang/recast.git#arrow-function-return-type" - } - } - } - } -} From 747dc5ba5219aae150e4de3846ff1c1bd6c03ae7 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 17 Jun 2016 17:15:13 -0700 Subject: [PATCH 14/64] dont sort and bind everything --- transforms/__testfixtures__/class.input.js | 38 +++++- transforms/__testfixtures__/class.output.js | 69 +++++++---- transforms/class.js | 129 ++++---------------- 3 files changed, 110 insertions(+), 126 deletions(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 0b395241..303c8256 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -80,13 +80,13 @@ var MyComponent3 = React.createClass({ }, autobindMe: function() {}, - dontAutobindMe: function(): number { return 12; }, + okBindMe: function(): number { return 12; }, // Function comment _renderRange: function(text: string, range, bla: Promise): ReactElement { var self = this; - self.dontAutobindMe(); + self.okBindMe(); call(self.autobindMe); var type = rage.type; @@ -132,3 +132,37 @@ module.exports = Relay.createContainer(MyComponent, { me: Relay.graphql`this is not graphql`, }, }); + +var MyComponent5 = React.createClass({ + getDefaultProps: function() { + return { + thisIs: true, + andThisIs: false, + }; + }, + + statics: {}, + + getInitialState: function() { + return { + todos: [], + }; + }, + + renderTodo: function(): ReactElement { + return ( +
+ {this.state.todos.map((item) =>

{item.text}

)} +
+ ); + }, + + render: function() { + return ( +
+

TODOs

+ {this.renderTodo()} +
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 28dbf343..794f4809 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -18,9 +18,9 @@ class MyComponent extends React.Component { }; } - foo(): void { + foo = (): void => { this.setState({heyoo: 24}); - } + }; } // Class comment @@ -34,6 +34,9 @@ class MyComponent2 extends React.Component { } class MyComponent3 extends React.Component { + static someThing = 10; + static funcThatDoesNothing = function(): void {}; + static propTypes = { highlightEntities: React.PropTypes.bool, linkifyEntities: React.PropTypes.bool, @@ -51,9 +54,6 @@ class MyComponent3 extends React.Component { }; }(); - static someThing = 10; - static funcThatDoesNothing = function(): void {}; - constructor(props, context) { super(props, context); props.foo(); @@ -68,13 +68,27 @@ class MyComponent3 extends React.Component { return ; }; + _renderImageRange = (text: string, range): ReactElement => { + var image = range.image; + if (image) { + return ( + + ); + } + }; + autobindMe = () => {}; + okBindMe = (): number => { return 12; }; // Function comment _renderRange = (text: string, range, bla: Promise): ReactElement => { var self = this; - self.dontAutobindMe(); + self.okBindMe(); call(self.autobindMe); var type = rage.type; @@ -96,21 +110,6 @@ class MyComponent3 extends React.Component { return text; }; - _renderImageRange(text: string, range): ReactElement { - var image = range.image; - if (image) { - return ( - - ); - } - } - - dontAutobindMe(): number { return 12; } - /* This is a comment */ render() { var content = this.props.text; @@ -135,3 +134,31 @@ module.exports = Relay.createContainer(MyComponent, { me: Relay.graphql`this is not graphql`, }, }); + +class MyComponent5 extends React.Component { + static defaultProps = { + thisIs: true, + andThisIs: false, + }; + + state = { + todos: [], + }; + + renderTodo = (): ReactElement => { + return ( +
+ {this.state.todos.map((item) =>

{item.text}

)} +
+ ); + }; + + render() { + return ( +
+

TODOs

+ {this.renderTodo()} +
+ ); + } +} diff --git a/transforms/class.js b/transforms/class.js index 32d826b1..befb6fb6 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -135,9 +135,6 @@ module.exports = (file, api, options) => { 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)); @@ -155,24 +152,23 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); - // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes` first; - // then simplifies `getDefaultProps` or converts it to an IIFE; - // finally it collects everything in the `statics` property object. + // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`; + // simplifies `getDefaultProps` or converts it to an IIFE; + // and collects everything else in the `statics` property object. const collectStatics = specPath => { - let result = specPath.properties.filter(property => - property.key && STATIC_KEYS[property.key.name] - ); - - const getDefaultProps = findGetDefaultProps(specPath); - if (getDefaultProps) { - result.push(createDefaultProps(getDefaultProps)); + const result = []; + + for (let i = 0; i < specPath.properties.length; i++) { + const property = specPath.properties[i]; + if (createFindPropFn('statics')(property) && property.value && property.value.properties) { + result.push(...property.value.properties); + } else if (createFindPropFn(DEFAULT_PROPS_FIELD)(property)) { + result.push(createDefaultProps(property)); + } else if (property.key && STATIC_KEYS[property.key.name]) { + result.push(property); + } } - const statics = specPath.properties.find(createFindPropFn('statics')); - result = result.concat( - (statics && statics.value && statics.value.properties) || [] - ); - return result; }; @@ -182,69 +178,6 @@ module.exports = (file, api, options) => { ) .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.` - findAutobindNamesFor(classPath, fnNames, j.thisExpression()).forEach(add); - - // Find `self.` 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); - }; - const findRequirePathAndBinding = (moduleName) => { let result = null; @@ -435,42 +368,34 @@ module.exports = (file, api, options) => { baseClassName, staticProperties, getInitialState, - autobindFunctionNames, methods, comments ) => { - let newConstructor = []; - const newProperties = []; + let maybeConstructor = []; + const initialStateProperty = []; if (isInitialStateLiftable(getInitialState)) { if (getInitialState) { - newProperties.push(convertInitialStateToClassProperty(getInitialState)); + initialStateProperty.push(convertInitialStateToClassProperty(getInitialState)); } } else { - newConstructor = createConstructor(getInitialState); + maybeConstructor = createConstructor(getInitialState); } - const arrowBindFunctions = []; - const newMethods = []; - - for (let i = 0; i < methods.length; i++) { - const method = methods[i]; - if (autobindFunctionNames.indexOf(method.key.name) !== -1) { - arrowBindFunctions.push(method); - } else { - newMethods.push(method); - } - } + const arrowBindFunctionsAndMethods = methods.map(method => + AUTOBIND_IGNORE_KEYS[method.key.name] ? + method : + createArrowPropertyFromMethod(method) + ); return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( [].concat( staticProperties, - newConstructor, - newProperties, - arrowBindFunctions.map(createArrowPropertyFromMethod), - newMethods + maybeConstructor, + initialStateProperty, + arrowBindFunctionsAndMethods ) ), j.memberExpression( @@ -531,7 +456,6 @@ module.exports = (file, api, options) => { const functions = collectFunctions(specPath); const comments = getComments(classPath); - const autobindFunctionNames = collectAutoBindFunctions(functions, classPath); const getInitialState = findGetInitialState(specPath); var path; @@ -554,7 +478,6 @@ module.exports = (file, api, options) => { baseClassName, staticProperties, getInitialState, - autobindFunctionNames, functions.map(createMethodDefinition), comments ) From c7136e2ab416ad60edcfd143127d7c1555f6151b Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 20 Jun 2016 14:17:42 -0700 Subject: [PATCH 15/64] prune unused requires safely and add support for primitives as class properties --- .../class-property-field.input.js | 28 ++++++ .../class-property-field.output.js | 26 ++++++ .../class-pure-mixin2.input.js | 2 + .../class-pure-mixin2.output.js | 2 + .../class-pure-mixin3.input.js | 0 .../class-pure-mixin3.output.js | 0 transforms/__tests__/class-test.js | 1 + transforms/class.js | 92 +++++++++++++------ 8 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 transforms/__testfixtures__/class-property-field.input.js create mode 100644 transforms/__testfixtures__/class-property-field.output.js delete mode 100644 transforms/__testfixtures__/class-pure-mixin3.input.js delete mode 100644 transforms/__testfixtures__/class-pure-mixin3.output.js diff --git a/transforms/__testfixtures__/class-property-field.input.js b/transforms/__testfixtures__/class-property-field.input.js new file mode 100644 index 00000000..e6ba4006 --- /dev/null +++ b/transforms/__testfixtures__/class-property-field.input.js @@ -0,0 +1,28 @@ +const React = require('react'); + +const Component1 = React.createClass({ + statics: { + booleanPrim: true, + numberPrim: 12, + stringPrim: 'foo', + nullPrim: null, + undefinedPrim: undefined, + }, + booleanPrim: true, + numberPrim: 12, + stringPrim: 'foo', + nullPrim: null, + undefinedPrim: undefined, + + foobar: function() { + return 123; + }, + + componentDidMount: function() { + console.log('hello'); + }, + + render: function() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class-property-field.output.js b/transforms/__testfixtures__/class-property-field.output.js new file mode 100644 index 00000000..34305a2b --- /dev/null +++ b/transforms/__testfixtures__/class-property-field.output.js @@ -0,0 +1,26 @@ +const React = require('react'); + +class Component1 extends React.Component { + static booleanPrim = true; + static numberPrim = 12; + static stringPrim = 'foo'; + static nullPrim = null; + static undefinedPrim = undefined; + booleanPrim = true; + numberPrim = 12; + stringPrim = 'foo'; + nullPrim = null; + undefinedPrim = undefined; + + foobar = () => { + return 123; + }; + + componentDidMount() { + console.log('hello'); + } + + render() { + return
; + } +} diff --git a/transforms/__testfixtures__/class-pure-mixin2.input.js b/transforms/__testfixtures__/class-pure-mixin2.input.js index cc920621..c09d29a5 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.input.js +++ b/transforms/__testfixtures__/class-pure-mixin2.input.js @@ -1,5 +1,6 @@ import React from 'React'; import WhateverYouCallIt from 'react-addons-pure-render-mixin'; +import dontPruneMe from 'foobar'; var ComponentWithOnlyPureRenderMixin = React.createClass({ mixins: [WhateverYouCallIt], @@ -11,6 +12,7 @@ var ComponentWithOnlyPureRenderMixin = React.createClass({ }, render: function() { + dontPruneMe(); return (
{this.state.counter}
); diff --git a/transforms/__testfixtures__/class-pure-mixin2.output.js b/transforms/__testfixtures__/class-pure-mixin2.output.js index dfaf9dfa..2778e1fe 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.output.js +++ b/transforms/__testfixtures__/class-pure-mixin2.output.js @@ -1,4 +1,5 @@ import React from 'React'; +import dontPruneMe from 'foobar'; class ComponentWithOnlyPureRenderMixin extends React.PureComponent { constructor(props, context) { @@ -10,6 +11,7 @@ class ComponentWithOnlyPureRenderMixin extends React.PureComponent { } render() { + dontPruneMe(); return (
{this.state.counter}
); diff --git a/transforms/__testfixtures__/class-pure-mixin3.input.js b/transforms/__testfixtures__/class-pure-mixin3.input.js deleted file mode 100644 index e69de29b..00000000 diff --git a/transforms/__testfixtures__/class-pure-mixin3.output.js b/transforms/__testfixtures__/class-pure-mixin3.output.js deleted file mode 100644 index e69de29b..00000000 diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index c3233662..f8f1c422 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -21,3 +21,4 @@ defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); defineTest(__dirname, 'class', null, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); defineTest(__dirname, 'class', null, 'class-pure-mixin2'); +defineTest(__dirname, 'class', null, 'class-property-field'); diff --git a/transforms/class.js b/transforms/class.js index befb6fb6..08c7b258 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -92,6 +92,7 @@ module.exports = (file, api, options) => { !filterDefaultPropsField(prop) && !filterGetInitialStateField(prop) && !isFunctionExpression(prop) && + !isPrimExpression(prop) && MIXIN_KEY != prop.key.name ) )); @@ -152,6 +153,16 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); + const isPrimExpression = node => ( + node.key && + node.key.type === 'Identifier' && + node.value && ( + node.value.type === 'Literal' || ( // TODO this might change in babylon v6 + node.value.type === 'Identifier' && + node.value.name === 'undefined' + )) + ); + // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`; // simplifies `getDefaultProps` or converts it to an IIFE; // and collects everything else in the `statics` property object. @@ -172,25 +183,20 @@ module.exports = (file, api, options) => { return result; }; - const collectFunctions = specPath => specPath.properties + const collectProperties = specPath => specPath.properties .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) - .filter(isFunctionExpression); + .filter(prop => isFunctionExpression(prop) || isPrimExpression(prop)); const findRequirePathAndBinding = (moduleName) => { let result = null; const requireStatement = root.find(j.VariableDeclarator, { + id: {type: 'Identifier'}, init: { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'require', - }, - arguments: [{ - value: moduleName, - }], + callee: {name: 'require'}, + arguments: [{value: moduleName}], }, }); @@ -344,13 +350,21 @@ module.exports = (file, api, options) => { false ), fn); - const createArrowPropertyFromMethod = method => + const createArrowProperty = prop => + withComments(j.classProperty( + j.identifier(prop.key.name), + createArrowFunctionExpression(prop.value), + null, + false + ), prop); + + const createClassProperty = prop => withComments(j.classProperty( - j.identifier(method.key.name), - createArrowFunctionExpression(method.value), + j.identifier(prop.key.name), + prop.value, null, false - ), method); + ), prop); // if there's no `getInitialState` or the `getInitialState` function is simple // (i.e., it does not reference `this.props`) then we don't need a constructor. @@ -368,7 +382,7 @@ module.exports = (file, api, options) => { baseClassName, staticProperties, getInitialState, - methods, + rawProperties, comments ) => { let maybeConstructor = []; @@ -382,11 +396,15 @@ module.exports = (file, api, options) => { maybeConstructor = createConstructor(getInitialState); } - const arrowBindFunctionsAndMethods = methods.map(method => - AUTOBIND_IGNORE_KEYS[method.key.name] ? - method : - createArrowPropertyFromMethod(method) - ); + const propertiesAndMethods = rawProperties.map(prop => { + if (isPrimExpression(prop)) { + return createClassProperty(prop); + } else if (AUTOBIND_IGNORE_KEYS[prop.key.name]) { + return createMethodDefinition(prop); + } + + return createArrowProperty(prop); + }); return withComments(j.classDeclaration( name ? j.identifier(name) : null, @@ -395,7 +413,7 @@ module.exports = (file, api, options) => { staticProperties, maybeConstructor, initialStateProperty, - arrowBindFunctionsAndMethods + propertiesAndMethods ) ), j.memberExpression( @@ -449,11 +467,30 @@ module.exports = (file, api, options) => { return null; }; + const findUnusedVariables = (path, varName) => j(path) + .closestScope() + .find(j.Identifier, {name: varName}) + // Ignore require vars + .filter(identifierPath => identifierPath.value !== path.value.id) + // Ignore import bindings + .filter(identifierPath => !( + path.value.type === 'ImportDeclaration' && + path.value.specifiers.some(specifier => specifier.local === identifierPath.value) + )) + // Ignore properties in MemberExpressions + .filter(identifierPath => { + const parent = identifierPath.parent.value; + return !( + j.MemberExpression.check(parent) && + parent.property === identifierPath.value + ); + }); + const updateToClass = (classPath, type) => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const name = ReactUtils.getComponentName(classPath); const statics = collectStatics(specPath); - const functions = collectFunctions(specPath); + const properties = collectProperties(specPath); const comments = getComments(classPath); const getInitialState = findGetInitialState(specPath); @@ -478,7 +515,7 @@ module.exports = (file, api, options) => { baseClassName, staticProperties, getInitialState, - functions.map(createMethodDefinition), + properties, comments ) ); @@ -496,7 +533,8 @@ module.exports = (file, api, options) => { if (!ReactUtils.hasMixins(classPath)) { return true; } else if (pureRenderMixinPathAndBinding) { - if (areMixinsConvertible([pureRenderMixinPathAndBinding.binding], classPath)) { + const {binding} = pureRenderMixinPathAndBinding; + if (areMixinsConvertible([binding], classPath)) { return true; } } @@ -526,8 +564,10 @@ module.exports = (file, api, options) => { if (didTransform) { // prune removed requires if (pureRenderMixinPathAndBinding) { - // FIXME check the scope before removing stuff - j(pureRenderMixinPathAndBinding.path).remove(); + const {binding, path} = pureRenderMixinPathAndBinding; + if (findUnusedVariables(path, binding).size() === 0) { + j(path).remove(); + } } return root.toSource(printOptions); From d4faea4988e514fec28b3622e593b86270d49124 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 20 Jun 2016 14:38:28 -0700 Subject: [PATCH 16/64] updated README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 81030d1d..fc4860e0 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,6 @@ jscodeshift -t react-codemod/transforms/sort-comp.js from the React API (lifecycle methods) and ignores functions that are being called directly (unless it is both called directly and passed around to somewhere else). - * TODO When `--no-super-class` is passed it only optionally extends - `React.Component` when `setState` or `forceUpdate` are used within the - class. ### Recast Options From cb88509d1d0476fee052319dcd6a4002abf36cb6 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 20 Jun 2016 16:00:09 -0700 Subject: [PATCH 17/64] updated constructor args (only use props/context when needed) --- .../class-pure-mixin1.output.js | 4 +-- .../class-pure-mixin2.output.js | 4 +-- .../__testfixtures__/class-test2.input.js | 18 ++++++++++++ .../__testfixtures__/class-test2.output.js | 28 ++++++++++++++++--- transforms/__testfixtures__/class.output.js | 8 +++--- transforms/class.js | 24 ++++++++++------ 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/transforms/__testfixtures__/class-pure-mixin1.output.js b/transforms/__testfixtures__/class-pure-mixin1.output.js index c85e4105..639fb610 100644 --- a/transforms/__testfixtures__/class-pure-mixin1.output.js +++ b/transforms/__testfixtures__/class-pure-mixin1.output.js @@ -1,8 +1,8 @@ var React = require('React'); class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); this.state = { counter: props.initialNumber + 1, diff --git a/transforms/__testfixtures__/class-pure-mixin2.output.js b/transforms/__testfixtures__/class-pure-mixin2.output.js index 2778e1fe..cbe50e0d 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.output.js +++ b/transforms/__testfixtures__/class-pure-mixin2.output.js @@ -2,8 +2,8 @@ import React from 'React'; import dontPruneMe from 'foobar'; class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); this.state = { counter: props.initialNumber + 1, diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index 04a1a247..42889b31 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -23,6 +23,24 @@ var ComponentWithNonSimpleInitialState = React.createClass({ }, }); +var ComponentWithBothPropsAndContextAccess = React.createClass({ + contextTypes: { + name: React.PropTypes.string, + }, + + getInitialState: function() { + return { + foo: this.props.foo, + }; + }, + + render: function() { + return ( +
{this.context.name}
+ ); + }, +}); + // Comment module.exports = React.createClass({ propTypes: { diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index f9c96fd8..9c18d163 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -8,8 +8,8 @@ class ComponentWithNonSimpleInitialState extends React.Component { static iDontKnowWhyYouNeedThis = true; // but comment it static foo = 'bar'; - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); this.state = { counter: props.initialNumber + 1, @@ -23,6 +23,26 @@ class ComponentWithNonSimpleInitialState extends React.Component { } } +class ComponentWithBothPropsAndContextAccess extends React.Component { + static contextTypes = { + name: React.PropTypes.string, + }; + + constructor(props, context) { + super(props, context); + + this.state = { + foo: props.foo, + }; + } + + render() { + return ( +
{this.context.name}
+ ); + } +} + // Comment module.exports = class extends React.Component { static propTypes = { @@ -33,8 +53,8 @@ module.exports = class extends React.Component { foo: 12, }; - constructor(props, context) { - super(props, context); + constructor() { + super(); // non-simple getInitialState var data = 'bar'; diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 794f4809..fcee22bd 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -9,8 +9,8 @@ var Image = require('Image.react'); * Multiline */ class MyComponent extends React.Component { - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); var x = props.foo; this.state = { @@ -54,8 +54,8 @@ class MyComponent3 extends React.Component { }; }(); - constructor(props, context) { - super(props, context); + constructor(props) { + super(props); props.foo(); this.state = { diff --git a/transforms/class.js b/transforms/class.js index 08c7b258..43db3a89 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -307,26 +307,32 @@ module.exports = (file, api, options) => { false ), getInitialState); - const createConstructorArgs = () => { - return [j.identifier('props'), j.identifier('context')]; + const createConstructorArgs = (hasPropsAccess, hasContextAccess) => { + if (hasContextAccess) { + return [j.identifier('props'), j.identifier('context')]; + } else if (hasPropsAccess) { + return [j.identifier('props')]; + } + return []; }; - const createConstructor = getInitialState => { - updatePropsAccess(getInitialState); + const createConstructor = (getInitialState, hasContextAccess) => { + const hasPropsAccess = updatePropsAccess(getInitialState).size() > 0; + const constructorArgs = createConstructorArgs(hasPropsAccess, hasContextAccess); return [ createMethodDefinition({ key: j.identifier('constructor'), value: j.functionExpression( null, - createConstructorArgs(), + constructorArgs, j.blockStatement( [].concat( [ j.expressionStatement( j.callExpression( j.identifier('super'), - [j.identifier('props'), j.identifier('context')] + constructorArgs ) ), ], @@ -387,13 +393,15 @@ module.exports = (file, api, options) => { ) => { let maybeConstructor = []; const initialStateProperty = []; - + const hasContextAccess = !!staticProperties.find((prop) => + prop.key.name === 'contextTypes' + ); if (isInitialStateLiftable(getInitialState)) { if (getInitialState) { initialStateProperty.push(convertInitialStateToClassProperty(getInitialState)); } } else { - maybeConstructor = createConstructor(getInitialState); + maybeConstructor = createConstructor(getInitialState, hasContextAccess); } const propertiesAndMethods = rawProperties.map(prop => { From 011371d07717694fa68c253044b4a2e8efde9c46 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 20 Jun 2016 23:45:35 -0700 Subject: [PATCH 18/64] added support for static methods --- transforms/__testfixtures__/class-test2.input.js | 3 +++ transforms/__testfixtures__/class-test2.output.js | 4 ++++ transforms/__testfixtures__/class.output.js | 2 +- transforms/class.js | 14 ++++++++++++-- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index 42889b31..d1420ede 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -8,6 +8,9 @@ var ComponentWithNonSimpleInitialState = React.createClass({ statics: { iDontKnowWhyYouNeedThis: true, // but comment it foo: 'bar', + dontBindMe: function(count: number): any { + return this; + }, }, getInitialState: function() { diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index 9c18d163..0a72735f 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -8,6 +8,10 @@ class ComponentWithNonSimpleInitialState extends React.Component { static iDontKnowWhyYouNeedThis = true; // but comment it static foo = 'bar'; + static dontBindMe(count: number): any { + return this; + } + constructor(props) { super(props); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index fcee22bd..25cb6e63 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -35,7 +35,7 @@ class MyComponent2 extends React.Component { class MyComponent3 extends React.Component { static someThing = 10; - static funcThatDoesNothing = function(): void {}; + static funcThatDoesNothing(): void {} static propTypes = { highlightEntities: React.PropTypes.bool, diff --git a/transforms/class.js b/transforms/class.js index 43db3a89..c3a22f6b 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -432,13 +432,23 @@ module.exports = (file, api, options) => { ), {comments}); }; - const createStaticClassProperty = staticProperty => - withComments(j.classProperty( + const createStaticClassProperty = staticProperty => { + if (staticProperty.value.type === 'FunctionExpression') { + return withComments(j.methodDefinition( + 'method', + j.identifier(staticProperty.key.name), + staticProperty.value, + true + ), staticProperty); + } + + return withComments(j.classProperty( j.identifier(staticProperty.key.name), staticProperty.value, null, true ), staticProperty); + }; const createStaticClassProperties = statics => statics.map(createStaticClassProperty); From c4c3c1aa6e81e115f303a2455ca66dadde61ab8d Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 21 Jun 2016 16:34:33 -0700 Subject: [PATCH 19/64] dont bind getChildContext --- .../__testfixtures__/class-test2.input.js | 31 +++++++++++++++++++ .../__testfixtures__/class-test2.output.js | 29 +++++++++++++++++ transforms/class.js | 1 + 3 files changed, 61 insertions(+) diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index d1420ede..cc4e87b9 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -83,3 +83,34 @@ var ComponentWithInconvertibleMixins = React.createClass({ ); }, }); + +// taken from https://facebook.github.io/react/docs/context.html#updating-context +var MediaQuery = React.createClass({ + childContextTypes: { + type: React.PropTypes.string, + }, + + getInitialState: function() { + return {type:'desktop'}; + }, + + getChildContext: function() { + return {type: this.state.type}; + }, + + componentDidMount: function() { + const checkMediaQuery = () => { + const type = window.matchMedia('(min-width: 1025px)').matches ? 'desktop' : 'mobile'; + if (type !== this.state.type) { + this.setState({type}); + } + }; + + window.addEventListener('resize', checkMediaQuery); + checkMediaQuery(); + }, + + render: function() { + return this.props.children; + }, +}); diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index 0a72735f..60554f34 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -87,3 +87,32 @@ var ComponentWithInconvertibleMixins = React.createClass({ ); }, }); + +// taken from https://facebook.github.io/react/docs/context.html#updating-context +class MediaQuery extends React.Component { + static childContextTypes = { + type: React.PropTypes.string, + }; + + state = {type:'desktop'}; + + getChildContext() { + return {type: this.state.type}; + } + + componentDidMount() { + const checkMediaQuery = () => { + const type = window.matchMedia('(min-width: 1025px)').matches ? 'desktop' : 'mobile'; + if (type !== this.state.type) { + this.setState({type}); + } + }; + + window.addEventListener('resize', checkMediaQuery); + checkMediaQuery(); + } + + render() { + return this.props.children; + } +} diff --git a/transforms/class.js b/transforms/class.js index c3a22f6b..df921fa0 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -27,6 +27,7 @@ module.exports = (file, api, options) => { componentWillMount: true, componentWillUpdate: true, componentWillUnmount: true, + getChildContext: true, getDefaultProps: true, getInitialState: true, render: true, From 3936f555c7f0da4c1ddc14cce20e11fd60fe552f Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 22 Jun 2016 01:26:57 -0700 Subject: [PATCH 20/64] handle constructor arguments properly --- .../class-initial-state.input.js | 108 ++++++++++++++++ .../class-initial-state.output.js | 115 ++++++++++++++++++ .../class-pure-mixin1.output.js | 10 +- .../class-pure-mixin2.output.js | 10 +- .../__testfixtures__/class-test2.input.js | 18 --- .../__testfixtures__/class-test2.output.js | 34 +----- transforms/__testfixtures__/class.input.js | 16 --- transforms/__testfixtures__/class.output.js | 18 --- transforms/__tests__/class-test.js | 1 + transforms/class.js | 70 ++++++----- 10 files changed, 276 insertions(+), 124 deletions(-) create mode 100644 transforms/__testfixtures__/class-initial-state.input.js create mode 100644 transforms/__testfixtures__/class-initial-state.output.js diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js new file mode 100644 index 00000000..edd502bc --- /dev/null +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -0,0 +1,108 @@ +import React from 'React'; + +/* + * Multiline + */ +var MyComponent = React.createClass({ + getInitialState: function() { + var x = this.props.foo; + return { + heyoo: 23, + }; + }, + + foo: function(): void { + this.setState({heyoo: 24}); + }, +}); + +var ComponentWithBothPropsAndContextAccess = React.createClass({ + contextTypes: { + name: React.PropTypes.string, + }, + + // we actually _don't_ need a constructor here since this will be + // initialized after a proper super(props, context) call. + // in other words, `this` will be ready when it reaches here. + getInitialState: function() { + return { + foo: this.props.foo, + bar: this.context.bar, + }; + }, + + render: function() { + return ( +
{this.context.name}
+ ); + }, +}); + +const App = React.createClass({ + getInitialState() { + const state = this.calculateState(); // _might_ use `this.context` + return state; + }, + calculateState() { + return { color: this.context.color }; + }, + render() { + return
; + }, +}); + +const App2 = React.createClass({ + getInitialState() { + const state = { + whatever: this.context.whatever, + }; + return state; + }, + render() { + return
; + }, +}); + +App.contextTypes = { + whatever: React.PropTypes.object, +}; + +var MyComponent2 = React.createClass({ + getInitialState: function() { + var x = this.props.foo.bar.wow.so.deep; + return { + heyoo: 23, + }; + }, + + foo: function(): void { + this.setState({heyoo: 24}); + }, +}); + +const getContextFromInstance = (x) => x.context; // meh + +var MyComponent3 = React.createClass({ + getInitialState: function() { + var x = getContextFromInstance(this); + return { + heyoo: x, + }; + }, + + foo: function(): void { + this.setState({heyoo: 24}); + }, +}); + +var MyComponent4 = React.createClass({ + getInitialState: function() { + return { + heyoo: getContextFromInstance(this), + }; + }, + + foo: function(): void { + this.setState({heyoo: 24}); + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js new file mode 100644 index 00000000..1a8ba0bd --- /dev/null +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -0,0 +1,115 @@ +import React from 'React'; + +/* + * Multiline + */ +class MyComponent extends React.Component { + constructor(props) { + super(props); + var x = props.foo; + + this.state = { + heyoo: 23, + }; + } + + foo = (): void => { + this.setState({heyoo: 24}); + }; +} + +class ComponentWithBothPropsAndContextAccess extends React.Component { + static contextTypes = { + name: React.PropTypes.string, + }; + + // we actually _don't_ need a constructor here since this will be + // initialized after a proper super(props, context) call. + // in other words, `this` will be ready when it reaches here. + state = { + foo: this.props.foo, + bar: this.context.bar, + }; + + render() { + return ( +
{this.context.name}
+ ); + } +} + +class App extends React.Component { + constructor(props, context) { + super(props, context); + const state = this.calculateState(); // _might_ use `this.context` + this.state = state; + } + + calculateState = () => { + return { color: this.context.color }; + }; + + render() { + return
; + } +} + +class App2 extends React.Component { + constructor(props, context) { + super(props, context); + const state = { + whatever: this.context.whatever, + }; + this.state = state; + } + + render() { + return
; + } +} + +App.contextTypes = { + whatever: React.PropTypes.object, +}; + +class MyComponent2 extends React.Component { + constructor(props) { + super(props); + var x = props.foo.bar.wow.so.deep; + + this.state = { + heyoo: 23, + }; + } + + foo = (): void => { + this.setState({heyoo: 24}); + }; +} + +const getContextFromInstance = (x) => x.context; // meh + +class MyComponent3 extends React.Component { + constructor(props, context) { + super(props, context); + var x = getContextFromInstance(this); + + this.state = { + heyoo: x, + }; + } + + foo = (): void => { + this.setState({heyoo: 24}); + }; +} + +class MyComponent4 extends React.Component { + state = { + heyoo: getContextFromInstance(this), + }; + + foo = (): void => { + this.setState({heyoo: 24}); + }; +} diff --git a/transforms/__testfixtures__/class-pure-mixin1.output.js b/transforms/__testfixtures__/class-pure-mixin1.output.js index 639fb610..5fdeef31 100644 --- a/transforms/__testfixtures__/class-pure-mixin1.output.js +++ b/transforms/__testfixtures__/class-pure-mixin1.output.js @@ -1,13 +1,9 @@ var React = require('React'); class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - counter: props.initialNumber + 1, - }; - } + state = { + counter: this.props.initialNumber + 1, + }; render() { return ( diff --git a/transforms/__testfixtures__/class-pure-mixin2.output.js b/transforms/__testfixtures__/class-pure-mixin2.output.js index cbe50e0d..98206728 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.output.js +++ b/transforms/__testfixtures__/class-pure-mixin2.output.js @@ -2,13 +2,9 @@ import React from 'React'; import dontPruneMe from 'foobar'; class ComponentWithOnlyPureRenderMixin extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - counter: props.initialNumber + 1, - }; - } + state = { + counter: this.props.initialNumber + 1, + }; render() { dontPruneMe(); diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index cc4e87b9..9ba87f93 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -26,24 +26,6 @@ var ComponentWithNonSimpleInitialState = React.createClass({ }, }); -var ComponentWithBothPropsAndContextAccess = React.createClass({ - contextTypes: { - name: React.PropTypes.string, - }, - - getInitialState: function() { - return { - foo: this.props.foo, - }; - }, - - render: function() { - return ( -
{this.context.name}
- ); - }, -}); - // Comment module.exports = React.createClass({ propTypes: { diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index 60554f34..ad4d80a5 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -12,37 +12,13 @@ class ComponentWithNonSimpleInitialState extends React.Component { return this; } - constructor(props) { - super(props); - - this.state = { - counter: props.initialNumber + 1, - }; - } - - render() { - return ( -
{this.state.counter}
- ); - } -} - -class ComponentWithBothPropsAndContextAccess extends React.Component { - static contextTypes = { - name: React.PropTypes.string, + state = { + counter: this.props.initialNumber + 1, }; - constructor(props, context) { - super(props, context); - - this.state = { - foo: props.foo, - }; - } - render() { return ( -
{this.context.name}
+
{this.state.counter}
); } } @@ -57,8 +33,8 @@ module.exports = class extends React.Component { foo: 12, }; - constructor() { - super(); + constructor(props) { + super(props); // non-simple getInitialState var data = 'bar'; diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 303c8256..2f997434 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -5,22 +5,6 @@ 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(): void { - this.setState({heyoo: 24}); - }, -}); - // Class comment var MyComponent2 = React.createClass({ getDefaultProps: function(): Object { diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 25cb6e63..3c38449c 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -5,24 +5,6 @@ var Relay = require('Relay'); var Image = require('Image.react'); -/* - * Multiline - */ -class MyComponent extends React.Component { - constructor(props) { - super(props); - var x = props.foo; - - this.state = { - heyoo: 23, - }; - } - - foo = (): void => { - this.setState({heyoo: 24}); - }; -} - // Class comment class MyComponent2 extends React.Component { static defaultProps = {a: 1}; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index f8f1c422..833d6f49 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -22,3 +22,4 @@ defineTest(__dirname, 'class', null, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); defineTest(__dirname, 'class', null, 'class-pure-mixin2'); defineTest(__dirname, 'class', null, 'class-property-field'); +defineTest(__dirname, 'class', null, 'class-initial-state'); diff --git a/transforms/class.js b/transforms/class.js index df921fa0..e790cf12 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -207,14 +207,14 @@ module.exports = (file, api, options) => { }, }); - if (importStatement.size() !== 0) { + if (importStatement.size()) { importStatement.forEach(path => { result = { path, binding: path.value.specifiers[0].local.name, }; }); - } else if (requireStatement.size() !== 0) { + } else if (requireStatement.size()) { requireStatement.forEach(path => { result = { path, @@ -240,21 +240,9 @@ module.exports = (file, api, options) => { const isInitialStateLiftable = getInitialState => { if (!getInitialState || !(getInitialState.value)) { return true; - } else if (!hasSingleReturnStatement(getInitialState.value)) { - return false; } - return j(getInitialState) - .find(j.MemberExpression, { - object: { - type: 'ThisExpression', - }, - property: { - type: 'Identifier', - name: 'props', - }, - }) - .size() === 0; + return hasSingleReturnStatementWithObject(getInitialState.value); }; const updatePropsAccess = getInitialState => @@ -290,7 +278,7 @@ module.exports = (file, api, options) => { }); const pickReturnValueOrCreateIIFE = value => { - if (hasSingleReturnStatement(value)) { + if (hasSingleReturnStatementWithObject(value)) { return value.body.body[0].argument; } else { return j.callExpression( @@ -308,18 +296,38 @@ module.exports = (file, api, options) => { false ), getInitialState); - const createConstructorArgs = (hasPropsAccess, hasContextAccess) => { + const createConstructorArgs = (hasContextAccess) => { if (hasContextAccess) { return [j.identifier('props'), j.identifier('context')]; - } else if (hasPropsAccess) { - return [j.identifier('props')]; } - return []; + + return [j.identifier('props')]; }; - const createConstructor = (getInitialState, hasContextAccess) => { - const hasPropsAccess = updatePropsAccess(getInitialState).size() > 0; - const constructorArgs = createConstructorArgs(hasPropsAccess, hasContextAccess); + const createConstructor = (getInitialState) => { + const initialStateAST = j(getInitialState); + let hasContextAccess = false; + + if ( + initialStateAST.find(j.MemberExpression, { // has `this.context` access + object: {type: 'ThisExpression'}, + property: {type: 'Identifier', name: 'context'}, + }).size() || + initialStateAST.find(j.CallExpression, { // a direct method call `this.x()` + callee: { + type: 'MemberExpression', + object: {type: 'ThisExpression'}, + }, + }).size() || + initialStateAST.find(j.MemberExpression, { // `this` is referenced alone + object: {type: 'ThisExpression'}, + }).size() !== initialStateAST.find(j.ThisExpression).size() + ) { + hasContextAccess = true; + } + + updatePropsAccess(getInitialState); + const constructorArgs = createConstructorArgs(hasContextAccess); return [ createMethodDefinition({ @@ -374,10 +382,16 @@ module.exports = (file, api, options) => { ), prop); // if there's no `getInitialState` or the `getInitialState` function is simple - // (i.e., it does not reference `this.props`) then we don't need a constructor. + // (i.e., it's just a return statement) then we don't need a constructor. // we can simply lift `state = {...}` as a property initializer. // otherwise, create a constructor and inline `this.state = ...`. // + // when we need to create a constructor, we only put `context` as the + // second parameter when the following things happen in `getInitialState()`: + // 1. there's a `this.context` access, or + // 2. there's a direct method call `this.x()`, or + // 3. `this` is referenced alone + // // It creates a class with the following order of properties/methods: // 1. static properties // 2. constructor (if necessary) @@ -394,15 +408,13 @@ module.exports = (file, api, options) => { ) => { let maybeConstructor = []; const initialStateProperty = []; - const hasContextAccess = !!staticProperties.find((prop) => - prop.key.name === 'contextTypes' - ); + if (isInitialStateLiftable(getInitialState)) { if (getInitialState) { initialStateProperty.push(convertInitialStateToClassProperty(getInitialState)); } } else { - maybeConstructor = createConstructor(getInitialState, hasContextAccess); + maybeConstructor = createConstructor(getInitialState); } const propertiesAndMethods = rawProperties.map(prop => { @@ -454,7 +466,7 @@ module.exports = (file, api, options) => { const createStaticClassProperties = statics => statics.map(createStaticClassProperty); - const hasSingleReturnStatement = value => ( + const hasSingleReturnStatementWithObject = value => ( value.type === 'FunctionExpression' && value.body && value.body.type === 'BlockStatement' && From ada3fb81819159241cd091ced8a7f24c0259e0e6 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 22 Jun 2016 02:35:34 -0700 Subject: [PATCH 21/64] fixed incorrect behavior when mixins is a non-array value --- .../__testfixtures__/class-test2.input.js | 18 ++++++++++++++++++ .../__testfixtures__/class-test2.output.js | 18 ++++++++++++++++++ transforms/class.js | 2 +- transforms/utils/ReactUtils.js | 11 +---------- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/transforms/__testfixtures__/class-test2.input.js b/transforms/__testfixtures__/class-test2.input.js index 9ba87f93..ae54847f 100644 --- a/transforms/__testfixtures__/class-test2.input.js +++ b/transforms/__testfixtures__/class-test2.input.js @@ -66,6 +66,24 @@ var ComponentWithInconvertibleMixins = React.createClass({ }, }); +var listOfInconvertibleMixins = [ReactComponentWithPureRenderMixin, FooBarMixin]; + +var ComponentWithInconvertibleMixins2 = React.createClass({ + mixins: listOfInconvertibleMixins, + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); + // taken from https://facebook.github.io/react/docs/context.html#updating-context var MediaQuery = React.createClass({ childContextTypes: { diff --git a/transforms/__testfixtures__/class-test2.output.js b/transforms/__testfixtures__/class-test2.output.js index ad4d80a5..d6253f1b 100644 --- a/transforms/__testfixtures__/class-test2.output.js +++ b/transforms/__testfixtures__/class-test2.output.js @@ -64,6 +64,24 @@ var ComponentWithInconvertibleMixins = React.createClass({ }, }); +var listOfInconvertibleMixins = [ReactComponentWithPureRenderMixin, FooBarMixin]; + +var ComponentWithInconvertibleMixins2 = React.createClass({ + mixins: listOfInconvertibleMixins, + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); + // taken from https://facebook.github.io/react/docs/context.html#updating-context class MediaQuery extends React.Component { static childContextTypes = { diff --git a/transforms/class.js b/transforms/class.js index e790cf12..2afaae18 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -558,7 +558,7 @@ module.exports = (file, api, options) => { // no mixins found on the classPath -> true // pure mixin identifier not found -> (has mixins) -> false // found pure mixin identifier -> - // class mixins only contain the identifier -> true + // class mixins is an array and only contains the identifier -> true // otherwise -> false const mixinsFilter = (classPath) => { if (!ReactUtils.hasMixins(classPath)) { diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index 83248702..a1c595ac 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -142,16 +142,7 @@ module.exports = function(j) { // --------------------------------------------------------------------------- // 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 isMixinProperty = property => property.key.name === 'mixins'; const hasMixins = classPath => { const spec = getReactCreateClassSpec(classPath); From 0cfad0acd405c5d39b7b7c99f45c5e862ffbb8d8 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 22 Jun 2016 03:55:13 -0700 Subject: [PATCH 22/64] fix early returns in getInitialState --- .../class-initial-state.input.js | 22 ++++++++++++++ .../class-initial-state.output.js | 27 +++++++++++++++++ transforms/class.js | 30 ++++++++++++------- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index edd502bc..d5b9905b 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -106,3 +106,25 @@ var MyComponent4 = React.createClass({ this.setState({heyoo: 24}); }, }); + +var Loader = React.createClass({ + getInitialState() { + if (this.props.stuff) { + return {x: 1}; + } else if (this.props.thing) { + return {x: 2}; + } + switch (this.props.wow) { + case 1: + return this.props.lol ? + {x: 3} : + this.whatever(this.props); + } + + return this.lol(); + }, + + render() { + return null; + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 1a8ba0bd..9801d59d 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -113,3 +113,30 @@ class MyComponent4 extends React.Component { this.setState({heyoo: 24}); }; } + +class Loader extends React.Component { + constructor(props, context) { + super(props, context); + if (props.stuff) { + this.state = {x: 1}; + return; + } else if (props.thing) { + this.state = {x: 2}; + return; + } + switch (props.wow) { + case 1: + this.state = props.lol ? + {x: 3} : + this.whatever(props); + + return; + } + + this.state = this.lol(); + } + + render() { + return null; + } +} diff --git a/transforms/class.js b/transforms/class.js index 2afaae18..ad1e0168 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -258,10 +258,13 @@ module.exports = (file, api, options) => { }) .forEach(path => j(path).replaceWith(j.identifier('props'))); - const inlineGetInitialState = getInitialState => - getInitialState.value.body.body.map(statement => { - if (statement.type === 'ReturnStatement') { - return j.expressionStatement( + const inlineGetInitialState = getInitialState => { + const functionExpressionAST = j(getInitialState.value); + + return functionExpressionAST + .find(j.ReturnStatement) + .forEach(path => { + j(path).replaceWith(j.expressionStatement( j.assignmentExpression( '=', j.memberExpression( @@ -269,13 +272,20 @@ module.exports = (file, api, options) => { j.identifier('state'), false ), - statement.argument + path.value.argument ) - ); - } - - return statement; - }); + )); + + if ( // FIXME is there a better way to check this? + j(path).closest(j.IfStatement).size() || + j(path).closest(j.SwitchStatement).size() || + j(path).closest(j.WhileStatement).size() || + j(path).closest(j.ForStatement).size() + ) { + j(path).insertAfter(j.returnStatement(null)); + } + }).getAST()[0].value.body.body; + }; const pickReturnValueOrCreateIIFE = value => { if (hasSingleReturnStatementWithObject(value)) { From 5f7b1b2fa6c40775ae4edc073bfc0a8fcbfc368e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 22 Jun 2016 22:06:54 -0700 Subject: [PATCH 23/64] WIP flow transformation; switched parser to Flow --- package.json | 2 +- .../__testfixtures__/class-proptypes.input.js | 37 ++++ .../class-proptypes.output.js | 56 +++++ transforms/__testfixtures__/class.input.js | 1 + transforms/__testfixtures__/class.output.js | 1 + transforms/__tests__/class-test.js | 3 +- transforms/class.js | 201 ++++++++++++++++-- transforms/utils/ReactUtils.js | 3 +- 8 files changed, 288 insertions(+), 16 deletions(-) create mode 100644 transforms/__testfixtures__/class-proptypes.input.js create mode 100644 transforms/__testfixtures__/class-proptypes.output.js diff --git a/package.json b/package.json index c056325c..74a148d1 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint ." }, "dependencies": { - "jscodeshift": "^0.3.24", + "jscodeshift": "^0.3.25", "babel-eslint": "^6.0.5", "babel-plugin-transform-object-rest-spread": "^6.6.5", "babel-preset-es2015": "^6.6.0", diff --git a/transforms/__testfixtures__/class-proptypes.input.js b/transforms/__testfixtures__/class-proptypes.input.js new file mode 100644 index 00000000..145fb2ba --- /dev/null +++ b/transforms/__testfixtures__/class-proptypes.input.js @@ -0,0 +1,37 @@ +/* @flow */ + +var React = require('react'); + +var Component = React.createClass({ + propTypes: { + optionalArray: React.PropTypes.array, + optionalBool: React.PropTypes.bool, + optionalFunc: React.PropTypes.func, + optionalNumber: React.PropTypes.number, + optionalObject: React.PropTypes.object, + optionalString: React.PropTypes.string, + optionalNode: React.PropTypes.node, + optionalElement: React.PropTypes.element, + optionalMessage: React.PropTypes.instanceOf(Message), + optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]), + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number, + }), + requiredFunc: React.PropTypes.func.isRequired, + requiredAny: React.PropTypes.any.isRequired, + }, + + render: function() { + return ( +
type safety
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-proptypes.output.js b/transforms/__testfixtures__/class-proptypes.output.js new file mode 100644 index 00000000..62dcaff0 --- /dev/null +++ b/transforms/__testfixtures__/class-proptypes.output.js @@ -0,0 +1,56 @@ +/* @flow */ + +var React = require('react'); + +class Component extends React.Component { + props: { + optionalArray?: Array, + optionalBool?: boolean, + optionalFunc?: Function, + optionalNumber?: number, + optionalObject?: Object, + optionalString?: string, + optionalNode?: any, + optionalElement?: any, + optionalMessage?: Message, + optionalEnum?: 'News' | 'Photos' | 1 | true | null, + optionalUnion?: string | number | Message, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number}, + optionalObjectWithShape?: {color: string, fontSize: number}, + requiredFunc: Function, + requiredAny: any, + }; + + static propTypes = { + optionalArray: React.PropTypes.array, + optionalBool: React.PropTypes.bool, + optionalFunc: React.PropTypes.func, + optionalNumber: React.PropTypes.number, + optionalObject: React.PropTypes.object, + optionalString: React.PropTypes.string, + optionalNode: React.PropTypes.node, + optionalElement: React.PropTypes.element, + optionalMessage: React.PropTypes.instanceOf(Message), + optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]), + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: React.PropTypes.number, + }), + requiredFunc: React.PropTypes.func.isRequired, + requiredAny: React.PropTypes.any.isRequired, + }; + + render() { + return ( +
type safety
+ ); + } +} diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 7e48bede..37cfcc3b 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -11,6 +11,7 @@ var MyComponent2 = React.createClass({ return {a: 1}; }, foo: function(): void { + const x = (a: Object, b: string): void => {}; // This code cannot be parsed by Babel v5 pass(this.foo); this.forceUpdate(); }, diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 1cb15dbf..9dce43f0 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -10,6 +10,7 @@ class MyComponent2 extends React.Component { static defaultProps = {a: 1}; foo = (): void => { + const x = (a: Object, b: string): void => {}; // This code cannot be parsed by Babel v5 pass(this.foo); this.forceUpdate(); }; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 833d6f49..a297feba 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -21,5 +21,6 @@ defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); defineTest(__dirname, 'class', null, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); defineTest(__dirname, 'class', null, 'class-pure-mixin2'); -defineTest(__dirname, 'class', null, 'class-property-field'); defineTest(__dirname, 'class', null, 'class-initial-state'); +defineTest(__dirname, 'class', null, 'class-property-field'); +defineTest(__dirname, 'class', { flow: true }, 'class-proptypes'); diff --git a/transforms/class.js b/transforms/class.js index ad1e0168..30345d38 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -93,7 +93,7 @@ module.exports = (file, api, options) => { !filterDefaultPropsField(prop) && !filterGetInitialStateField(prop) && !isFunctionExpression(prop) && - !isPrimExpression(prop) && + !isPrimProperty(prop) && MIXIN_KEY != prop.key.name ) )); @@ -154,16 +154,19 @@ module.exports = (file, api, options) => { node.value.type === 'FunctionExpression' ); - const isPrimExpression = node => ( - node.key && - node.key.type === 'Identifier' && - node.value && ( - node.value.type === 'Literal' || ( // TODO this might change in babylon v6 - node.value.type === 'Identifier' && - node.value.name === 'undefined' - )) + const isPrimProperty = prop => ( + prop.key && + prop.key.type === 'Identifier' && + prop.value && + isPrimExpression(prop.value) ); + const isPrimExpression = node => ( + node.type === 'Literal' || ( // TODO this might change in babylon v6 + node.type === 'Identifier' && + node.name === 'undefined' + )); + // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`; // simplifies `getDefaultProps` or converts it to an IIFE; // and collects everything else in the `statics` property object. @@ -188,7 +191,7 @@ module.exports = (file, api, options) => { .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) - .filter(prop => isFunctionExpression(prop) || isPrimExpression(prop)); + .filter(prop => isFunctionExpression(prop) || isPrimProperty(prop)); const findRequirePathAndBinding = (moduleName) => { let result = null; @@ -211,7 +214,7 @@ module.exports = (file, api, options) => { importStatement.forEach(path => { result = { path, - binding: path.value.specifiers[0].local.name, + binding: path.value.specifiers[0].id.name, }; }); } else if (requireStatement.size()) { @@ -391,6 +394,169 @@ module.exports = (file, api, options) => { false ), prop); + // --------------------------------------------------------------------------- + // Flow! + + const flowAnyType = j.anyTypeAnnotation(); + + const literalToFlowType = node => { + switch (typeof node.value) { + case 'string': + return j.stringLiteralTypeAnnotation(node.value, node.raw); + case 'number': + return j.numberLiteralTypeAnnotation(node.value, node.raw); + case 'boolean': + return j.booleanLiteralTypeAnnotation(node.value, node.raw); + case 'object': + return j.nullLiteralTypeAnnotation(); + default: + return flowAnyType; // meh + } + }; + + const propTypeToFlowMapping = { + // prim types + any: flowAnyType, + array: j.genericTypeAnnotation( + j.identifier('Array'), + j.typeParameterInstantiation([flowAnyType]) + ), + bool: j.booleanTypeAnnotation(), + element: flowAnyType, + func: j.genericTypeAnnotation( + j.identifier('Function'), + null + ), + node: flowAnyType, + number: j.numberTypeAnnotation(), + object: j.genericTypeAnnotation( + j.identifier('Object'), + null + ), + string: j.stringTypeAnnotation(), + + // type classes + arrayOf: (type) => j.genericTypeAnnotation( + j.identifier('Array'), + j.typeParameterInstantiation([type]) + ), + instanceOf: (type) => j.genericTypeAnnotation( + type, + null + ), + objectOf: (type) => j.objectTypeAnnotation( + [], + [j.objectTypeIndexer(j.identifier('key'), j.stringTypeAnnotation(), type)], + [] + ), + oneOf: (typeList) => j.unionTypeAnnotation(typeList), + oneOfType: (typeList) => j.unionTypeAnnotation(typeList), + }; + + const propTypeToFlowAnnotation = val => { + let cursor = val; + let isOptional = true; + let typeResult = flowAnyType; + + if ( + cursor.type === 'MemberExpression' && + cursor.property.type === 'Identifier' && + cursor.property.name === 'isRequired' + ) { + isOptional = false; + cursor = cursor.object; + } + + if ( + cursor.type === 'CallExpression' + ) { + switch (cursor.callee.property.name) { + case 'arrayOf': { + const arg = cursor.arguments[0]; + const constructor = propTypeToFlowMapping['arrayOf']; + typeResult = constructor( + propTypeToFlowAnnotation(arg)[0] + ); + break; + } + case 'instanceOf': { + const arg = cursor.arguments[0]; + if (arg.type !== 'Identifier') { + typeResult = flowAnyType; + break; + } + + const constructor = propTypeToFlowMapping['instanceOf']; + typeResult = constructor(arg); + break; + } + case 'objectOf': { + const arg = cursor.arguments[0]; + const constructor = propTypeToFlowMapping['objectOf']; + typeResult = constructor( + propTypeToFlowAnnotation(arg)[0] + ); + break; + } + case 'oneOf': { + const argList = cursor.arguments[0].elements; + if (!argList.every(node => node.type === 'Literal')) { + typeResult = flowAnyType; + break; + } + const constructor = propTypeToFlowMapping['oneOf']; + typeResult = constructor( + argList.map(literalToFlowType) + ); + break; + } + case 'oneOfType': { + const argList = cursor.arguments[0].elements; + const constructor = propTypeToFlowMapping['oneOfType']; + typeResult = constructor( + argList.map(arg => propTypeToFlowAnnotation(arg)[0]) + ); + break; + } + case 'shape': { // TODO + + } + } + } else if ( + cursor.type === 'MemberExpression' && + cursor.property.type === 'Identifier' + ) { + typeResult = propTypeToFlowMapping[cursor.property.name] || flowAnyType; + } + + return [typeResult, isOptional]; + }; + + const createFlowAnnotationsFromPropTypesProperties = (prop) => { + const typeProperty = []; + + if (!prop) { + return typeProperty; + } + + prop.value.properties.forEach(typeProp => { + const name = typeProp.key.name; + const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); + typeProperty.push(j.objectTypeProperty( + j.identifier(name), + valueType, + isOptional + )); + }); + + return j.classProperty( + j.identifier('props'), + null, + j.typeAnnotation(j.objectTypeAnnotation(typeProperty)), + false + ); + }; + // if there's no `getInitialState` or the `getInitialState` function is simple // (i.e., it's just a return statement) then we don't need a constructor. // we can simply lift `state = {...}` as a property initializer. @@ -428,7 +594,7 @@ module.exports = (file, api, options) => { } const propertiesAndMethods = rawProperties.map(prop => { - if (isPrimExpression(prop)) { + if (isPrimProperty(prop)) { return createClassProperty(prop); } else if (AUTOBIND_IGNORE_KEYS[prop.key.name]) { return createMethodDefinition(prop); @@ -437,10 +603,17 @@ module.exports = (file, api, options) => { return createArrowProperty(prop); }); + const flowPropsAnnotation = options['flow'] ? + createFlowAnnotationsFromPropTypesProperties( + staticProperties.find((path) => path.key.name === 'propTypes') + ) : + []; + return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( [].concat( + flowPropsAnnotation, staticProperties, maybeConstructor, initialStateProperty, @@ -516,7 +689,7 @@ module.exports = (file, api, options) => { // Ignore import bindings .filter(identifierPath => !( path.value.type === 'ImportDeclaration' && - path.value.specifiers.some(specifier => specifier.local === identifierPath.value) + path.value.specifiers.some(specifier => specifier.id === identifierPath.value) )) // Ignore properties in MemberExpressions .filter(identifierPath => { @@ -618,3 +791,5 @@ module.exports = (file, api, options) => { return null; }; + +module.exports.parser = 'flow'; diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index c51ffa05..dae4d4bb 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -58,7 +58,8 @@ module.exports = function(j) { .filter(decl => findReactCreateClassCallExpression(decl).size() > 0); const findReactCreateClassExportDefault = path => - path.find(j.ExportDefaultDeclaration, { + path.find(j.ExportDeclaration, { + default: true, declaration: { type: 'CallExpression', callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, From a7f71a3d4665af3e894206b375c89f9dd30c1663 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 01:12:00 -0700 Subject: [PATCH 24/64] flow works now --- npm-shrinkwrap.json | 12 +++ ...roptypes.input.js => class-flow1.input.js} | 0 ...ptypes.output.js => class-flow1.output.js} | 7 +- .../__testfixtures__/class-flow2.input.js | 52 +++++++++++ .../__testfixtures__/class-flow2.output.js | 54 ++++++++++++ .../__testfixtures__/class-flow3.input.js | 48 ++++++++++ .../__testfixtures__/class-flow3.output.js | 68 ++++++++++++++ .../__testfixtures__/class-flow4.input.js | 44 ++++++++++ .../__testfixtures__/class-flow4.output.js | 44 ++++++++++ transforms/__tests__/class-test.js | 13 +-- transforms/class.js | 88 +++++++++++++------ 11 files changed, 395 insertions(+), 35 deletions(-) create mode 100644 npm-shrinkwrap.json rename transforms/__testfixtures__/{class-proptypes.input.js => class-flow1.input.js} (100%) rename transforms/__testfixtures__/{class-proptypes.output.js => class-flow1.output.js} (92%) create mode 100644 transforms/__testfixtures__/class-flow2.input.js create mode 100644 transforms/__testfixtures__/class-flow2.output.js create mode 100644 transforms/__testfixtures__/class-flow3.input.js create mode 100644 transforms/__testfixtures__/class-flow3.output.js create mode 100644 transforms/__testfixtures__/class-flow4.input.js create mode 100644 transforms/__testfixtures__/class-flow4.output.js diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 00000000..f42f9d46 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "jscodeshift": { + "version": "0.3.25", + "dependencies": { + "recast": { + "version": "git://github.com/keyanzhang/recast.git#temporary-codemod-fork" + } + } + } + } +} diff --git a/transforms/__testfixtures__/class-proptypes.input.js b/transforms/__testfixtures__/class-flow1.input.js similarity index 100% rename from transforms/__testfixtures__/class-proptypes.input.js rename to transforms/__testfixtures__/class-flow1.input.js diff --git a/transforms/__testfixtures__/class-proptypes.output.js b/transforms/__testfixtures__/class-flow1.output.js similarity index 92% rename from transforms/__testfixtures__/class-proptypes.output.js rename to transforms/__testfixtures__/class-flow1.output.js index 62dcaff0..bb283e14 100644 --- a/transforms/__testfixtures__/class-proptypes.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -16,8 +16,11 @@ class Component extends React.Component { optionalEnum?: 'News' | 'Photos' | 1 | true | null, optionalUnion?: string | number | Message, optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number}, - optionalObjectWithShape?: {color: string, fontSize: number}, + optionalObjectOf?: {[key: string]: number,}, + optionalObjectWithShape?: { + color?: string, + fontSize?: number, + }, requiredFunc: Function, requiredAny: any, }; diff --git a/transforms/__testfixtures__/class-flow2.input.js b/transforms/__testfixtures__/class-flow2.input.js new file mode 100644 index 00000000..490706c6 --- /dev/null +++ b/transforms/__testfixtures__/class-flow2.input.js @@ -0,0 +1,52 @@ +/* code taken from https://github.com/reactjs/react-router/blob/master/modules/IndexRoute.js */ +/* @flow */ + +import React from 'react' +import warning from './routerWarning' +import invariant from 'invariant' +import { createRouteFromReactElement } from './RouteUtils' +import { component, components, falsy } from './InternalPropTypes' + +const { func } = React.PropTypes + +/** + * An is used to specify its parent's in + * a JSX route config. + */ +const IndexRoute = React.createClass({ + + statics: { + + createRouteFromReactElement(element, parentRoute) { + /* istanbul ignore else: sanity check */ + if (parentRoute) { + parentRoute.indexRoute = createRouteFromReactElement(element) + } else { + warning( + false, + 'An does not make sense at the root of your route config' + ) + } + } + + }, + + propTypes: { + path: falsy, + component, + components, + getComponent: func, + getComponents: func + }, + + /* istanbul ignore next: sanity check */ + render() { + invariant( + false, + ' elements are for router configuration only and should not be rendered' + ) + } + +}) + +export default IndexRoute diff --git a/transforms/__testfixtures__/class-flow2.output.js b/transforms/__testfixtures__/class-flow2.output.js new file mode 100644 index 00000000..f4345014 --- /dev/null +++ b/transforms/__testfixtures__/class-flow2.output.js @@ -0,0 +1,54 @@ +/* code taken from https://github.com/reactjs/react-router/blob/master/modules/IndexRoute.js */ +/* @flow */ + +import React from 'react' +import warning from './routerWarning' +import invariant from 'invariant' +import { createRouteFromReactElement } from './RouteUtils' +import { component, components, falsy } from './InternalPropTypes' + +const { func } = React.PropTypes + +/** + * An is used to specify its parent's in + * a JSX route config. + */ +class IndexRoute extends React.Component { + props: { + path?: any, + component?: any, + components?: any, + getComponent?: any, + getComponents?: any, + }; + + static createRouteFromReactElement(element, parentRoute) { + /* istanbul ignore else: sanity check */ + if (parentRoute) { + parentRoute.indexRoute = createRouteFromReactElement(element) + } else { + warning( + false, + 'An does not make sense at the root of your route config' + ) + } + } + + static propTypes = { + path: falsy, + component, + components, + getComponent: func, + getComponents: func + }; + + /* istanbul ignore next: sanity check */ + render() { + invariant( + false, + ' elements are for router configuration only and should not be rendered' + ) + } +} + +export default IndexRoute diff --git a/transforms/__testfixtures__/class-flow3.input.js b/transforms/__testfixtures__/class-flow3.input.js new file mode 100644 index 00000000..cb19b550 --- /dev/null +++ b/transforms/__testfixtures__/class-flow3.input.js @@ -0,0 +1,48 @@ +/* @flow */ + +var React = require('react'); +var {PropTypes} = React; + +var getPropTypes = () => PropTypes.string; + +var myUnionPropType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Message), +]); + +var spreadMe = { + optionalArray: PropTypes.array, + optionalBool: PropTypes.bool, +}; + +var optionalFuncShortHand = PropTypes.func; + +var Component = React.createClass({ + propTypes: { + ...spreadMe, + optionalFuncShortHand, + optionalNumber: 1 + 1 === 2 ? PropTypes.number : PropTypes.string, + optionalObject: PropTypes.object, + optionalString: getPropTypes(), + optionalNode: PropTypes.node, + optionalElement: PropTypes.element, + optionalMessage: PropTypes.instanceOf(Message), + optionalEnum: PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalUnion: myUnionPropType, + optionalArrayOf: PropTypes.arrayOf(PropTypes.number), + optionalObjectOf: PropTypes.objectOf(PropTypes.number), + optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number, + }), + requiredFunc: PropTypes.func.isRequired, + requiredAny: PropTypes.any.isRequired, + }, + + render: function() { + return ( +
type safety
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js new file mode 100644 index 00000000..d8ca839a --- /dev/null +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -0,0 +1,68 @@ +/* @flow */ + +var React = require('react'); +var {PropTypes} = React; + +var getPropTypes = () => PropTypes.string; + +var myUnionPropType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Message), +]); + +var spreadMe = { + optionalArray: PropTypes.array, + optionalBool: PropTypes.bool, +}; + +var optionalFuncShortHand = PropTypes.func; + +class Component extends React.Component { + props: { + optionalFuncShortHand?: any, + optionalNumber?: any, + optionalObject?: Object, + optionalString?: any, + optionalNode?: any, + optionalElement?: any, + optionalMessage?: Message, + optionalEnum?: 'News' | 'Photos' | 1 | true | null, + optionalUnion?: any, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number,}, + optionalObjectWithShape?: { + color?: string, + fontSize?: number, + }, + requiredFunc: Function, + requiredAny: any, + }; + + static propTypes = { + ...spreadMe, + optionalFuncShortHand, + optionalNumber: 1 + 1 === 2 ? PropTypes.number : PropTypes.string, + optionalObject: PropTypes.object, + optionalString: getPropTypes(), + optionalNode: PropTypes.node, + optionalElement: PropTypes.element, + optionalMessage: PropTypes.instanceOf(Message), + optionalEnum: PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalUnion: myUnionPropType, + optionalArrayOf: PropTypes.arrayOf(PropTypes.number), + optionalObjectOf: PropTypes.objectOf(PropTypes.number), + optionalObjectWithShape: PropTypes.shape({ + color: PropTypes.string, + fontSize: PropTypes.number, + }), + requiredFunc: PropTypes.func.isRequired, + requiredAny: PropTypes.any.isRequired, + }; + + render() { + return ( +
type safety
+ ); + } +} diff --git a/transforms/__testfixtures__/class-flow4.input.js b/transforms/__testfixtures__/class-flow4.input.js new file mode 100644 index 00000000..9694eb60 --- /dev/null +++ b/transforms/__testfixtures__/class-flow4.input.js @@ -0,0 +1,44 @@ +/* @flow */ + +var React = require('react'); +var {PropTypes} = React; + +var myUnionPropType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Message), +]); + +var spreadMe = { + optionalArray: PropTypes.array, + optionalBool: PropTypes.bool, +}; + +var optionalFuncShortHand = PropTypes.func; + +var Component = React.createClass({ + propTypes: Object.assign({}, { + ...spreadMe, + optionalFuncShortHand, + optionalNumber: PropTypes.number, + optionalObject: PropTypes.object, + }), + + render: function() { + return ( +
type safety
+ ); + }, +}); + +var thatPropTypes = {}; + +var Component2 = React.createClass({ + propTypes: thatPropTypes, + + render: function() { + return ( +
type safety
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-flow4.output.js b/transforms/__testfixtures__/class-flow4.output.js new file mode 100644 index 00000000..a25c6254 --- /dev/null +++ b/transforms/__testfixtures__/class-flow4.output.js @@ -0,0 +1,44 @@ +/* @flow */ + +var React = require('react'); +var {PropTypes} = React; + +var myUnionPropType = PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.instanceOf(Message), +]); + +var spreadMe = { + optionalArray: PropTypes.array, + optionalBool: PropTypes.bool, +}; + +var optionalFuncShortHand = PropTypes.func; + +class Component extends React.Component { + static propTypes = Object.assign({}, { + ...spreadMe, + optionalFuncShortHand, + optionalNumber: PropTypes.number, + optionalObject: PropTypes.object, + }); + + render() { + return ( +
type safety
+ ); + } +} + +var thatPropTypes = {}; + +class Component2 extends React.Component { + static propTypes = thatPropTypes; + + render() { + return ( +
type safety
+ ); + } +} diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index a297feba..a59c434b 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -18,9 +18,12 @@ const pureMixinAlternativeOption = { defineTest(__dirname, 'class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); -defineTest(__dirname, 'class', null, 'export-default-class'); +defineTest(__dirname, 'class', { flow: true }, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); -defineTest(__dirname, 'class', null, 'class-pure-mixin2'); -defineTest(__dirname, 'class', null, 'class-initial-state'); -defineTest(__dirname, 'class', null, 'class-property-field'); -defineTest(__dirname, 'class', { flow: true }, 'class-proptypes'); +defineTest(__dirname, 'class', { flow: true }, 'class-pure-mixin2'); +defineTest(__dirname, 'class', { flow: true }, 'class-initial-state'); +defineTest(__dirname, 'class', { flow: true }, 'class-property-field'); +defineTest(__dirname, 'class', { flow: true }, 'class-flow1'); +defineTest(__dirname, 'class', { flow: true }, 'class-flow2'); +defineTest(__dirname, 'class', { flow: true }, 'class-flow3'); +defineTest(__dirname, 'class', { flow: true }, 'class-flow4'); diff --git a/transforms/class.js b/transforms/class.js index 30345d38..74a7cd6a 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -60,6 +60,19 @@ module.exports = (file, api, options) => { const MIXIN_KEY = 'mixins'; + let shouldTransformFlow = false; + + if (options['flow']) { + const programBodyNode = root.find(j.Program).get('body', 0).node; + if (programBodyNode && programBodyNode.comments) { + programBodyNode.comments.forEach(node => { + if (node.value.indexOf('@flow') !== -1) { + shouldTransformFlow = true; + } + }); + } + } + // --------------------------------------------------------------------------- // Checks if the module uses mixins or accesses deprecated APIs. const checkDeprecatedAPICalls = classPath => @@ -407,7 +420,7 @@ module.exports = (file, api, options) => { return j.numberLiteralTypeAnnotation(node.value, node.raw); case 'boolean': return j.booleanLiteralTypeAnnotation(node.value, node.raw); - case 'object': + case 'object': // we already know it's a NullLiteral here return j.nullLiteralTypeAnnotation(); default: return flowAnyType; // meh @@ -444,13 +457,12 @@ module.exports = (file, api, options) => { type, null ), - objectOf: (type) => j.objectTypeAnnotation( - [], - [j.objectTypeIndexer(j.identifier('key'), j.stringTypeAnnotation(), type)], - [] - ), + objectOf: (type) => j.objectTypeAnnotation([], [ + j.objectTypeIndexer(j.identifier('key'), j.stringTypeAnnotation(), type) + ]), oneOf: (typeList) => j.unionTypeAnnotation(typeList), oneOfType: (typeList) => j.unionTypeAnnotation(typeList), + shape: (propList) => j.objectTypeAnnotation(propList), }; const propTypeToFlowAnnotation = val => { @@ -458,7 +470,7 @@ module.exports = (file, api, options) => { let isOptional = true; let typeResult = flowAnyType; - if ( + if ( // check `.isRequired` first cursor.type === 'MemberExpression' && cursor.property.type === 'Identifier' && cursor.property.name === 'isRequired' @@ -467,13 +479,20 @@ module.exports = (file, api, options) => { cursor = cursor.object; } - if ( - cursor.type === 'CallExpression' - ) { + if (cursor.type === 'CallExpression') { // type class + const calleeName = cursor.callee.type === 'MemberExpression' ? + cursor.callee.property.name : + cursor.callee.name; + + const constructor = propTypeToFlowMapping[calleeName]; + if (!constructor) { + typeResult = flowAnyType; + return [typeResult, isOptional]; + } + switch (cursor.callee.property.name) { case 'arrayOf': { const arg = cursor.arguments[0]; - const constructor = propTypeToFlowMapping['arrayOf']; typeResult = constructor( propTypeToFlowAnnotation(arg)[0] ); @@ -486,13 +505,11 @@ module.exports = (file, api, options) => { break; } - const constructor = propTypeToFlowMapping['instanceOf']; typeResult = constructor(arg); break; } case 'objectOf': { const arg = cursor.arguments[0]; - const constructor = propTypeToFlowMapping['objectOf']; typeResult = constructor( propTypeToFlowAnnotation(arg)[0] ); @@ -502,27 +519,38 @@ module.exports = (file, api, options) => { const argList = cursor.arguments[0].elements; if (!argList.every(node => node.type === 'Literal')) { typeResult = flowAnyType; - break; + } else { + typeResult = constructor( + argList.map(literalToFlowType) + ); } - const constructor = propTypeToFlowMapping['oneOf']; - typeResult = constructor( - argList.map(literalToFlowType) - ); break; } case 'oneOfType': { const argList = cursor.arguments[0].elements; - const constructor = propTypeToFlowMapping['oneOfType']; typeResult = constructor( argList.map(arg => propTypeToFlowAnnotation(arg)[0]) ); break; } - case 'shape': { // TODO - + case 'shape': { + const rawPropList = cursor.arguments[0].properties; + const flowPropList = []; + rawPropList.forEach(typeProp => { + const name = typeProp.key.name; + const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); + flowPropList.push(j.objectTypeProperty( + j.identifier(name), + valueType, + isOptional + )); + }); + + typeResult = constructor(flowPropList); + break; } } - } else if ( + } else if ( // prim type cursor.type === 'MemberExpression' && cursor.property.type === 'Identifier' ) { @@ -533,16 +561,20 @@ module.exports = (file, api, options) => { }; const createFlowAnnotationsFromPropTypesProperties = (prop) => { - const typeProperty = []; + const typePropertyList = []; - if (!prop) { - return typeProperty; + if (!prop || prop.value.type !== 'ObjectExpression') { + return typePropertyList; } prop.value.properties.forEach(typeProp => { + if (!typeProp.key) { // SpreadProperty + return; + } + const name = typeProp.key.name; const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); - typeProperty.push(j.objectTypeProperty( + typePropertyList.push(j.objectTypeProperty( j.identifier(name), valueType, isOptional @@ -552,7 +584,7 @@ module.exports = (file, api, options) => { return j.classProperty( j.identifier('props'), null, - j.typeAnnotation(j.objectTypeAnnotation(typeProperty)), + j.typeAnnotation(j.objectTypeAnnotation(typePropertyList)), false ); }; @@ -603,7 +635,7 @@ module.exports = (file, api, options) => { return createArrowProperty(prop); }); - const flowPropsAnnotation = options['flow'] ? + const flowPropsAnnotation = shouldTransformFlow ? createFlowAnnotationsFromPropTypesProperties( staticProperties.find((path) => path.key.name === 'propTypes') ) : From 231f6297eb47b1b5841d0cb62122fd2060af2af2 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 15:22:23 -0700 Subject: [PATCH 25/64] added support for flow property initializers --- .../__testfixtures__/class-flow5.input.js | 35 +++++++++++++++++ .../__testfixtures__/class-flow5.output.js | 34 +++++++++++++++++ transforms/__tests__/class-test.js | 1 + transforms/class.js | 38 +++++++++++++++++-- 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 transforms/__testfixtures__/class-flow5.input.js create mode 100644 transforms/__testfixtures__/class-flow5.output.js diff --git a/transforms/__testfixtures__/class-flow5.input.js b/transforms/__testfixtures__/class-flow5.input.js new file mode 100644 index 00000000..ddab97c5 --- /dev/null +++ b/transforms/__testfixtures__/class-flow5.input.js @@ -0,0 +1,35 @@ +/* @flow */ + +var React = require('react'); + +var Component = React.createClass({ + statics: { + notTyped: true, + numberOrBool: (true: number | boolean), + logger: (x: any): void => { console.log(x); }, + logger2: function(x: any): void { + console.log(x); + }, + }, + + notTyped: true, + foo: (12: number), + bar: ('2000': string), + handleClick: (null: ?(evt: any) => void), + + doStuff: function(x: number, y: boolean): boolean { + return y && (x > 0); + }, + + componentDidMount: function() { + this.handleClick = function(e) { + console.log(e); + }; + }, + + render: function() { + return ( +
{this.foo}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-flow5.output.js b/transforms/__testfixtures__/class-flow5.output.js new file mode 100644 index 00000000..5b3faac1 --- /dev/null +++ b/transforms/__testfixtures__/class-flow5.output.js @@ -0,0 +1,34 @@ +/* @flow */ + +var React = require('react'); + +class Component extends React.Component { + static notTyped = true; + static numberOrBool: number | boolean = true; + static logger = (x: any): void => { console.log(x); }; + + static logger2(x: any): void { + console.log(x); + } + + notTyped = true; + foo: number = 12; + bar: string = '2000'; + handleClick: ?(evt: any) => void = null; + + doStuff = (x: number, y: boolean): boolean => { + return y && (x > 0); + }; + + componentDidMount() { + this.handleClick = function(e) { + console.log(e); + }; + } + + render() { + return ( +
{this.foo}
+ ); + } +} diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index a59c434b..b66965c5 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -27,3 +27,4 @@ defineTest(__dirname, 'class', { flow: true }, 'class-flow1'); defineTest(__dirname, 'class', { flow: true }, 'class-flow2'); defineTest(__dirname, 'class', { flow: true }, 'class-flow3'); defineTest(__dirname, 'class', { flow: true }, 'class-flow4'); +defineTest(__dirname, 'class', { flow: true }, 'class-flow5'); diff --git a/transforms/class.js b/transforms/class.js index 74a7cd6a..34371a29 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -107,6 +107,7 @@ module.exports = (file, api, options) => { !filterGetInitialStateField(prop) && !isFunctionExpression(prop) && !isPrimProperty(prop) && + !isPrimPropertyWithTypeAnnotation(prop) && MIXIN_KEY != prop.key.name ) )); @@ -174,8 +175,16 @@ module.exports = (file, api, options) => { isPrimExpression(prop.value) ); + const isPrimPropertyWithTypeAnnotation = prop => ( + prop.key && + prop.key.type === 'Identifier' && + prop.value && + prop.value.type === 'TypeCastExpression' && + isPrimExpression(prop.value.expression) + ); + const isPrimExpression = node => ( - node.type === 'Literal' || ( // TODO this might change in babylon v6 + node.type === 'Literal' || ( // NOTE this might change in babylon v6 node.type === 'Identifier' && node.name === 'undefined' )); @@ -204,7 +213,11 @@ module.exports = (file, api, options) => { .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) - .filter(prop => isFunctionExpression(prop) || isPrimProperty(prop)); + .filter(prop => + isFunctionExpression(prop) || + isPrimPropertyWithTypeAnnotation(prop) || + isPrimProperty(prop) + ); const findRequirePathAndBinding = (moduleName) => { let result = null; @@ -407,6 +420,14 @@ module.exports = (file, api, options) => { false ), prop); + const createClassPropertyWithType = prop => + withComments(j.classProperty( + j.identifier(prop.key.name), + prop.value.expression, + prop.value.typeAnnotation, + false + ), prop); + // --------------------------------------------------------------------------- // Flow! @@ -626,7 +647,9 @@ module.exports = (file, api, options) => { } const propertiesAndMethods = rawProperties.map(prop => { - if (isPrimProperty(prop)) { + if (isPrimPropertyWithTypeAnnotation(prop)) { + return createClassPropertyWithType(prop); + } else if (isPrimProperty(prop)) { return createClassProperty(prop); } else if (AUTOBIND_IGNORE_KEYS[prop.key.name]) { return createMethodDefinition(prop); @@ -670,6 +693,15 @@ module.exports = (file, api, options) => { ), staticProperty); } + if (staticProperty.value.type === 'TypeCastExpression') { + return withComments(j.classProperty( + j.identifier(staticProperty.key.name), + staticProperty.value.expression, + staticProperty.value.typeAnnotation, + true + ), staticProperty); + } + return withComments(j.classProperty( j.identifier(staticProperty.key.name), staticProperty.value, From b8bb77a74bac3d2df64f85ac5e5e3af955c0987c Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 16:51:43 -0700 Subject: [PATCH 26/64] better way to detect early returns in getInitialState --- transforms/class.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/transforms/class.js b/transforms/class.js index 34371a29..19cec82b 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -293,6 +293,13 @@ module.exports = (file, api, options) => { return functionExpressionAST .find(j.ReturnStatement) .forEach(path => { + let shouldInsertReturnAfterAssignment = false; + + // if the return statement is not a direct child of the function body + if (getInitialState.value.body.body.indexOf(path.value) === -1) { + shouldInsertReturnAfterAssignment = true; + } + j(path).replaceWith(j.expressionStatement( j.assignmentExpression( '=', @@ -305,12 +312,7 @@ module.exports = (file, api, options) => { ) )); - if ( // FIXME is there a better way to check this? - j(path).closest(j.IfStatement).size() || - j(path).closest(j.SwitchStatement).size() || - j(path).closest(j.WhileStatement).size() || - j(path).closest(j.ForStatement).size() - ) { + if (shouldInsertReturnAfterAssignment) { j(path).insertAfter(j.returnStatement(null)); } }).getAST()[0].value.body.body; From e00677fc18ea731987485c54cf80756c36ad73b4 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 18:23:05 -0700 Subject: [PATCH 27/64] bail out if user uses getInitialState or getDefaultProps elsewhere --- .../class-initial-state.input.js | 30 +++++++++++++++++++ .../class-initial-state.output.js | 30 +++++++++++++++++++ transforms/class.js | 22 ++++++++++++-- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index d5b9905b..2a9d49e4 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -128,3 +128,33 @@ var Loader = React.createClass({ return null; }, }); + +var helper = () => {}; + +var PassGetInitialState = React.createClass({ + getInitialState() { + return this.lol(); + }, + + helper1: function() { + helper(this.getInitialState); + }, + + render() { + return null; + }, +}); + +var UseGetInitialState = React.createClass({ + getInitialState() { + return this.lol(); + }, + + helper2() { + this.setState(this.getInitialState()); + }, + + render() { + return null; + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 9801d59d..e0d9392b 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -140,3 +140,33 @@ class Loader extends React.Component { return null; } } + +var helper = () => {}; + +var PassGetInitialState = React.createClass({ + getInitialState() { + return this.lol(); + }, + + helper1: function() { + helper(this.getInitialState); + }, + + render() { + return null; + }, +}); + +var UseGetInitialState = React.createClass({ + getInitialState() { + return this.lol(); + }, + + helper2() { + this.setState(this.getInitialState()); + }, + + render() { + return null; + }, +}); diff --git a/transforms/class.js b/transforms/class.js index 19cec82b..b9cdf698 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -84,7 +84,7 @@ module.exports = (file, api, options) => { 0 ) > 0; - const callsDeprecatedAPIs = classPath => { + const hasNoCallsToDeprecatedAPIs = classPath => { if (checkDeprecatedAPICalls(classPath)) { console.log( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + @@ -97,6 +97,23 @@ module.exports = (file, api, options) => { return true; }; + const hasNoCallsToAPIsThatWillBeRemoved = classPath => { + const hasInvalidCalls = ( + j(classPath).find(j.Identifier, {name: DEFAULT_PROPS_FIELD}).size() > 1 || + j(classPath).find(j.Identifier, {name: GET_INITIAL_STATE_FIELD}).size() > 1 + ); + if (hasInvalidCalls) { + console.log( + file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + 'was skipped because of API calls that will be removed. Remove calls to `' + + DEFAULT_PROPS_FIELD + '` and/or `' + GET_INITIAL_STATE_FIELD + + '` 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 => ( @@ -828,7 +845,8 @@ module.exports = (file, api, options) => { const apply = (path, type) => path .filter(mixinsFilter) - .filter(callsDeprecatedAPIs) + .filter(hasNoCallsToDeprecatedAPIs) + .filter(hasNoCallsToAPIsThatWillBeRemoved) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); From 59e36711689ead9d400e1b6a15f77aaf0ef14272 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 18:38:06 -0700 Subject: [PATCH 28/64] bail out if arguments is found --- .../class-initial-state.input.js | 10 ++++++++ .../class-initial-state.output.js | 10 ++++++++ transforms/class.js | 25 ++++++++++++++++--- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index 2a9d49e4..fa8337ea 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -158,3 +158,13 @@ var UseGetInitialState = React.createClass({ return null; }, }); + +var UseArguments = React.createClass({ + helper() { + console.log(arguments); + }, + + render() { + return null; + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index e0d9392b..8d3b9294 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -170,3 +170,13 @@ var UseGetInitialState = React.createClass({ return null; }, }); + +var UseArguments = React.createClass({ + helper() { + console.log(arguments); + }, + + render() { + return null; + }, +}); diff --git a/transforms/class.js b/transforms/class.js index b9cdf698..abf8b03e 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -86,7 +86,7 @@ module.exports = (file, api, options) => { const hasNoCallsToDeprecatedAPIs = classPath => { if (checkDeprecatedAPICalls(classPath)) { - console.log( + console.warn( 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 ' + @@ -103,7 +103,7 @@ module.exports = (file, api, options) => { j(classPath).find(j.Identifier, {name: GET_INITIAL_STATE_FIELD}).size() > 1 ); if (hasInvalidCalls) { - console.log( + console.warn( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + 'was skipped because of API calls that will be removed. Remove calls to `' + DEFAULT_PROPS_FIELD + '` and/or `' + GET_INITIAL_STATE_FIELD + @@ -114,6 +114,22 @@ module.exports = (file, api, options) => { return true; }; + const doesNotUseArguments = classPath => { + const hasArguments = ( + j(classPath).find(j.Identifier, {name: 'arguments'}).size() > 0 + ); + if (hasArguments) { + console.warn( + file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + 'was skipped because `arguments` was found in your functions. ' + + 'Arrow functions do not expose an `arguments` object; ' + + 'consider changing to use ES6 spread operator and re-run this script.' + ); + return false; + } + return true; + }; + const canConvertToClass = classPath => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const invalidProperties = specPath.properties.filter(prop => ( @@ -133,7 +149,7 @@ module.exports = (file, api, options) => { const invalidText = invalidProperties .map(prop => prop.key.name ? prop.key.name : prop.key) .join(', '); - console.log( + console.warn( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + 'was skipped because of invalid field(s) `' + invalidText + '` on ' + 'the React component. Remove any right-hand-side expressions that ' + @@ -835,7 +851,7 @@ module.exports = (file, api, options) => { return true; } } - console.log( + console.warn( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + 'was skipped because of inconvertible mixins.' ); @@ -847,6 +863,7 @@ module.exports = (file, api, options) => { .filter(mixinsFilter) .filter(hasNoCallsToDeprecatedAPIs) .filter(hasNoCallsToAPIsThatWillBeRemoved) + .filter(doesNotUseArguments) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); From 4f64cc2d5ae4096cfc41376c0553597a02f7464e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 20:04:53 -0700 Subject: [PATCH 29/64] defer state property initializer evaluation when necessary --- .../class-initial-state.input.js | 12 +++++++++++ .../class-initial-state.output.js | 17 ++++++++++++---- transforms/class.js | 20 +++++++++++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index fa8337ea..62bb9879 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -129,6 +129,18 @@ var Loader = React.createClass({ }, }); +var DeferStateInitialization = React.createClass({ + getInitialState() { + return {x: this.something}; + }, + + something: 42, + + render() { + return
; + }, +}); + var helper = () => {}; var PassGetInitialState = React.createClass({ diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 8d3b9294..97182df3 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -105,13 +105,13 @@ class MyComponent3 extends React.Component { } class MyComponent4 extends React.Component { - state = { - heyoo: getContextFromInstance(this), - }; - foo = (): void => { this.setState({heyoo: 24}); }; + + state = { + heyoo: getContextFromInstance(this), + }; } class Loader extends React.Component { @@ -141,6 +141,15 @@ class Loader extends React.Component { } } +class DeferStateInitialization extends React.Component { + something = 42; + state = {x: this.something}; + + render() { + return
; + } +} + var helper = () => {}; var PassGetInitialState = React.createClass({ diff --git a/transforms/class.js b/transforms/class.js index abf8b03e..d90a4bea 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -645,6 +645,23 @@ module.exports = (file, api, options) => { ); }; + // to ensure that our property initializers' evaluation order is safe + const repositionStateProperty = (initialStateProperty, propertiesAndMethods) => { + if (j(initialStateProperty).find(j.ThisExpression).size() === 0) { + return initialStateProperty.concat(propertiesAndMethods); + } + + const result = [].concat(propertiesAndMethods); + let lastPropPosition = result.length - 1; + + while (lastPropPosition >= 0 && result[lastPropPosition].kind === 'method') { + lastPropPosition--; + } + + result.splice(lastPropPosition + 1, 0, initialStateProperty[0]); + return result; + }; + // if there's no `getInitialState` or the `getInitialState` function is simple // (i.e., it's just a return statement) then we don't need a constructor. // we can simply lift `state = {...}` as a property initializer. @@ -706,8 +723,7 @@ module.exports = (file, api, options) => { flowPropsAnnotation, staticProperties, maybeConstructor, - initialStateProperty, - propertiesAndMethods + repositionStateProperty(initialStateProperty, propertiesAndMethods) ) ), j.memberExpression( From d67b6a805f2461c65cb2732cee206d9e99d7774b Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 21:11:34 -0700 Subject: [PATCH 30/64] no shadowing in constructor --- .../class-initial-state.input.js | 23 ++++++ .../class-initial-state.output.js | 22 ++++++ transforms/class.js | 72 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index 62bb9879..b8407b0e 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -180,3 +180,26 @@ var UseArguments = React.createClass({ return null; }, }); + +var ShadowingIssue = React.createClass({ + getInitialState() { + const props = { x: 123 }; + return { x: props.x }; + }, + + render() { + return null; + }, +}); + +var ShadowingButFine = React.createClass({ + getInitialState() { + const props = this.props; + const context = this.context; + return { x: props.x + context.x }; + }, + + render() { + return null; + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 97182df3..c35744bf 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -189,3 +189,25 @@ var UseArguments = React.createClass({ return null; }, }); + +var ShadowingIssue = React.createClass({ + getInitialState() { + const props = { x: 123 }; + return { x: props.x }; + }, + + render() { + return null; + }, +}); + +class ShadowingButFine extends React.Component { + constructor(props, context) { + super(props, context); + this.state = { x: props.x + context.x }; + } + + render() { + return null; + } +} diff --git a/transforms/class.js b/transforms/class.js index d90a4bea..fcd1b4f7 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -130,6 +130,68 @@ module.exports = (file, api, options) => { return true; }; + const isGetInitialStateConstructorSafe = getInitialState => { + if (!getInitialState) { + return true; + } + + const collection = j(getInitialState); + let result = true; + + const propsVarDeclarationCount = collection.find(j.VariableDeclarator, { + id: {name: 'props'}, + }).size(); + + const contextVarDeclarationCount = collection.find(j.VariableDeclarator, { + id: {name: 'context'}, + }).size(); + + if ( + propsVarDeclarationCount && + propsVarDeclarationCount !== collection.find(j.VariableDeclarator, { + id: {name: 'props'}, + init: { + type: 'MemberExpression', + object: {type: 'ThisExpression'}, + property: {name: 'props'}, + } + }).size() + ) { + result = false; + } + + if ( + contextVarDeclarationCount && + contextVarDeclarationCount !== collection.find(j.VariableDeclarator, { + id: {name: 'context'}, + init: { + type: 'MemberExpression', + object: {type: 'ThisExpression'}, + property: {name: 'context'}, + } + }).size() + ) { + result = false; + } + + return result; + }; + + const isInitialStateConvertible = classPath => { + const result = isGetInitialStateConstructorSafe( + ReactUtils.getReactCreateClassSpec(classPath) + ); + if (!result) { + console.warn( + file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + 'was skipped because of potential shadowing issues were found in ' + + 'the React component. Rename variable declarations of `props` and/or `context` ' + + 'in your `getInitialState` and re-run this script.' + ); + } + return result; + }; + const canConvertToClass = classPath => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const invalidProperties = specPath.properties.filter(prop => ( @@ -323,6 +385,15 @@ module.exports = (file, api, options) => { const inlineGetInitialState = getInitialState => { const functionExpressionAST = j(getInitialState.value); + // at this point if there exists bindings like `const props = ...`, we + // already know the RHS must be `this.props` (see `isGetInitialStateConstructorSafe`) + // so it's safe to just remove them + functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'props'}}) + .forEach(path => j(path).remove()); + + functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'context'}}) + .forEach(path => j(path).remove()); + return functionExpressionAST .find(j.ReturnStatement) .forEach(path => { @@ -880,6 +951,7 @@ module.exports = (file, api, options) => { .filter(hasNoCallsToDeprecatedAPIs) .filter(hasNoCallsToAPIsThatWillBeRemoved) .filter(doesNotUseArguments) + .filter(isInitialStateConvertible) .filter(canConvertToClass) .forEach(classPath => updateToClass(classPath, type)); From 3cc6d71f0db76c3e6f3bde1c86f2d9d5a43d1dc0 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 22:46:28 -0700 Subject: [PATCH 31/64] handle inner function declarations in getInitialState correctly --- .../class-initial-state.input.js | 60 +++++++++++++++--- .../class-initial-state.output.js | 63 ++++++++++++++++--- transforms/class.js | 28 +++++++-- 3 files changed, 126 insertions(+), 25 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index b8407b0e..fbfe41a5 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -1,8 +1,6 @@ import React from 'React'; -/* - * Multiline - */ +// only needs props var MyComponent = React.createClass({ getInitialState: function() { var x = this.props.foo; @@ -21,7 +19,7 @@ var ComponentWithBothPropsAndContextAccess = React.createClass({ name: React.PropTypes.string, }, - // we actually _don't_ need a constructor here since this will be + // we actually don't need a constructor here since this will be // initialized after a proper super(props, context) call. // in other words, `this` will be ready when it reaches here. getInitialState: function() { @@ -54,7 +52,7 @@ const App = React.createClass({ const App2 = React.createClass({ getInitialState() { const state = { - whatever: this.context.whatever, + whatever: this.context.whatever, // needs context }; return state; }, @@ -84,7 +82,7 @@ const getContextFromInstance = (x) => x.context; // meh var MyComponent3 = React.createClass({ getInitialState: function() { - var x = getContextFromInstance(this); + var x = getContextFromInstance(this); // `this` is referenced alone return { heyoo: x, }; @@ -95,6 +93,8 @@ var MyComponent3 = React.createClass({ }, }); +// we are not sure what you'll need from `this`, +// so it's safe to defer `state`'s initialization var MyComponent4 = React.createClass({ getInitialState: function() { return { @@ -107,6 +107,7 @@ var MyComponent4 = React.createClass({ }, }); +// intense control flow testing var Loader = React.createClass({ getInitialState() { if (this.props.stuff) { @@ -120,6 +121,17 @@ var Loader = React.createClass({ {x: 3} : this.whatever(this.props); } + for (let i = 0; i < 100; i++) { + if (i === 20) { + return {x: i}; + } + } + + try { + doSomeThingReallyBad(); + } catch (e) { + return {error: e}; + } return this.lol(); }, @@ -129,6 +141,33 @@ var Loader = React.createClass({ }, }); +var FunctionDeclarationInGetInitialState = React.createClass({ + getInitialState() { + function func() { + var x = 1; + return x; // dont change me + } + + const foo = () => { + return 120; // dont change me + }; + + var q = function() { + return 100; // dont change me + }; + + return { + x: func(), + y: foo(), + z: q(), + }; + }, + + render() { + return null; + }, +}); + var DeferStateInitialization = React.createClass({ getInitialState() { return {x: this.something}; @@ -143,7 +182,7 @@ var DeferStateInitialization = React.createClass({ var helper = () => {}; -var PassGetInitialState = React.createClass({ +var PassGetInitialState = React.createClass({ // bail out here getInitialState() { return this.lol(); }, @@ -157,7 +196,7 @@ var PassGetInitialState = React.createClass({ }, }); -var UseGetInitialState = React.createClass({ +var UseGetInitialState = React.createClass({ // bail out here getInitialState() { return this.lol(); }, @@ -171,7 +210,7 @@ var UseGetInitialState = React.createClass({ }, }); -var UseArguments = React.createClass({ +var UseArguments = React.createClass({ // bail out here helper() { console.log(arguments); }, @@ -181,7 +220,7 @@ var UseArguments = React.createClass({ }, }); -var ShadowingIssue = React.createClass({ +var ShadowingIssue = React.createClass({ // bail out here getInitialState() { const props = { x: 123 }; return { x: props.x }; @@ -192,6 +231,7 @@ var ShadowingIssue = React.createClass({ }, }); +// will remove unnecessary bindings var ShadowingButFine = React.createClass({ getInitialState() { const props = this.props; diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index c35744bf..07a8bd49 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -1,8 +1,6 @@ import React from 'React'; -/* - * Multiline - */ +// only needs props class MyComponent extends React.Component { constructor(props) { super(props); @@ -23,7 +21,7 @@ class ComponentWithBothPropsAndContextAccess extends React.Component { name: React.PropTypes.string, }; - // we actually _don't_ need a constructor here since this will be + // we actually don't need a constructor here since this will be // initialized after a proper super(props, context) call. // in other words, `this` will be ready when it reaches here. state = { @@ -58,7 +56,7 @@ class App2 extends React.Component { constructor(props, context) { super(props, context); const state = { - whatever: this.context.whatever, + whatever: this.context.whatever, // needs context }; this.state = state; } @@ -92,7 +90,7 @@ const getContextFromInstance = (x) => x.context; // meh class MyComponent3 extends React.Component { constructor(props, context) { super(props, context); - var x = getContextFromInstance(this); + var x = getContextFromInstance(this); // `this` is referenced alone this.state = { heyoo: x, @@ -104,6 +102,8 @@ class MyComponent3 extends React.Component { }; } +// we are not sure what you'll need from `this`, +// so it's safe to defer `state`'s initialization class MyComponent4 extends React.Component { foo = (): void => { this.setState({heyoo: 24}); @@ -114,6 +114,7 @@ class MyComponent4 extends React.Component { }; } +// intense control flow testing class Loader extends React.Component { constructor(props, context) { super(props, context); @@ -132,6 +133,19 @@ class Loader extends React.Component { return; } + for (let i = 0; i < 100; i++) { + if (i === 20) { + this.state = {x: i}; + return; + } + } + + try { + doSomeThingReallyBad(); + } catch (e) { + this.state = {error: e}; + return; + } this.state = this.lol(); } @@ -141,6 +155,34 @@ class Loader extends React.Component { } } +class FunctionDeclarationInGetInitialState extends React.Component { + constructor(props) { + super(props); + function func() { + var x = 1; + return x; // dont change me + } + + const foo = () => { + return 120; // dont change me + }; + + var q = function() { + return 100; // dont change me + }; + + this.state = { + x: func(), + y: foo(), + z: q(), + }; + } + + render() { + return null; + } +} + class DeferStateInitialization extends React.Component { something = 42; state = {x: this.something}; @@ -152,7 +194,7 @@ class DeferStateInitialization extends React.Component { var helper = () => {}; -var PassGetInitialState = React.createClass({ +var PassGetInitialState = React.createClass({ // bail out here getInitialState() { return this.lol(); }, @@ -166,7 +208,7 @@ var PassGetInitialState = React.createClass({ }, }); -var UseGetInitialState = React.createClass({ +var UseGetInitialState = React.createClass({ // bail out here getInitialState() { return this.lol(); }, @@ -180,7 +222,7 @@ var UseGetInitialState = React.createClass({ }, }); -var UseArguments = React.createClass({ +var UseArguments = React.createClass({ // bail out here helper() { console.log(arguments); }, @@ -190,7 +232,7 @@ var UseArguments = React.createClass({ }, }); -var ShadowingIssue = React.createClass({ +var ShadowingIssue = React.createClass({ // bail out here getInitialState() { const props = { x: 123 }; return { x: props.x }; @@ -201,6 +243,7 @@ var ShadowingIssue = React.createClass({ }, }); +// will remove unnecessary bindings class ShadowingButFine extends React.Component { constructor(props, context) { super(props, context); diff --git a/transforms/class.js b/transforms/class.js index fcd1b4f7..7e32de22 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -383,23 +383,41 @@ module.exports = (file, api, options) => { .forEach(path => j(path).replaceWith(j.identifier('props'))); const inlineGetInitialState = getInitialState => { - const functionExpressionAST = j(getInitialState.value); + const functionExpressionCollection = j(getInitialState.value); // at this point if there exists bindings like `const props = ...`, we // already know the RHS must be `this.props` (see `isGetInitialStateConstructorSafe`) // so it's safe to just remove them - functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'props'}}) + functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'props'}}) .forEach(path => j(path).remove()); - functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'context'}}) + functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'context'}}) .forEach(path => j(path).remove()); - return functionExpressionAST + return functionExpressionCollection .find(j.ReturnStatement) + .filter(path => { + // filter out inner function declarations here (helper functions, promises, etc.). + const mainBodyCollection = j(getInitialState.value.body); + return ( + mainBodyCollection + .find(j.ArrowFunctionExpression) + .find(j.ReturnStatement, path.value) + .size() === 0 && + mainBodyCollection + .find(j.FunctionDeclaration) + .find(j.ReturnStatement, path.value) + .size() === 0 && + mainBodyCollection + .find(j.FunctionExpression) + .find(j.ReturnStatement, path.value) + .size() === 0 + ); + }) .forEach(path => { let shouldInsertReturnAfterAssignment = false; - // if the return statement is not a direct child of the function body + // if the return statement is not a direct child of getInitialState's body if (getInitialState.value.body.body.indexOf(path.value) === -1) { shouldInsertReturnAfterAssignment = true; } From f06bcd66ad932af7e77c596c1c09e60e168ec464 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 23 Jun 2016 23:08:26 -0700 Subject: [PATCH 32/64] fix lint errors --- .eslintrc.js | 4 + transforms/class.js | 176 ++++++++++++++++----------------- transforms/sort-comp.js | 6 +- transforms/utils/ReactUtils.js | 24 ++--- 4 files changed, 107 insertions(+), 103 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2b5ea3d6..605a3f43 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,4 +10,8 @@ module.exports = { ecmaFeatures: { modules: false }, + + rules: { + 'no-use-before-define': 2, + }, }; diff --git a/transforms/class.js b/transforms/class.js index 7e32de22..3ed60f3a 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -73,6 +73,75 @@ module.exports = (file, api, options) => { } } + // --------------------------------------------------------------------------- + // 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 findGetInitialState = specPath => + specPath.properties.find(createFindPropFn(GET_INITIAL_STATE_FIELD)); + + const withComments = (to, from) => { + to.comments = from.comments; + return to; + }; + + const isPrimExpression = node => ( + node.type === 'Literal' || ( // NOTE this might change in babylon v6 + node.type === 'Identifier' && + node.name === 'undefined' + )); + + const isFunctionExpression = node => ( + node.key && + node.key.type === 'Identifier' && + node.value && + node.value.type === 'FunctionExpression' + ); + + const isPrimProperty = prop => ( + prop.key && + prop.key.type === 'Identifier' && + prop.value && + isPrimExpression(prop.value) + ); + + const isPrimPropertyWithTypeAnnotation = prop => ( + prop.key && + prop.key.type === 'Identifier' && + prop.value && + prop.value.type === 'TypeCastExpression' && + isPrimExpression(prop.value.expression) + ); + + const hasSingleReturnStatementWithObject = 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 isInitialStateLiftable = getInitialState => { + if (!getInitialState || !(getInitialState.value)) { + return true; + } + + return hasSingleReturnStatementWithObject(getInitialState.value); + }; + // --------------------------------------------------------------------------- // Checks if the module uses mixins or accesses deprecated APIs. const checkDeprecatedAPICalls = classPath => @@ -232,57 +301,28 @@ module.exports = (file, api, options) => { 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 findGetInitialState = specPath => - specPath.properties.find(createFindPropFn(GET_INITIAL_STATE_FIELD)); - - 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 isPrimProperty = prop => ( - prop.key && - prop.key.type === 'Identifier' && - prop.value && - isPrimExpression(prop.value) - ); - - const isPrimPropertyWithTypeAnnotation = prop => ( - prop.key && - prop.key.type === 'Identifier' && - prop.value && - prop.value.type === 'TypeCastExpression' && - isPrimExpression(prop.value.expression) - ); + const pickReturnValueOrCreateIIFE = value => { + if (hasSingleReturnStatementWithObject(value)) { + return value.body.body[0].argument; + } else { + return j.callExpression( + value, + [] + ); + } + }; - const isPrimExpression = node => ( - node.type === 'Literal' || ( // NOTE this might change in babylon v6 - node.type === 'Identifier' && - node.name === 'undefined' - )); + const createDefaultProps = prop => + withComments( + j.property( + 'init', + j.identifier(DEFAULT_PROPS_KEY), + pickReturnValueOrCreateIIFE(prop.value) + ), + prop + ); // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`; // simplifies `getDefaultProps` or converts it to an IIFE; @@ -361,14 +401,6 @@ module.exports = (file, api, options) => { fn.value ), fn); - const isInitialStateLiftable = getInitialState => { - if (!getInitialState || !(getInitialState.value)) { - return true; - } - - return hasSingleReturnStatementWithObject(getInitialState.value); - }; - const updatePropsAccess = getInitialState => j(getInitialState) .find(j.MemberExpression, { @@ -440,17 +472,6 @@ module.exports = (file, api, options) => { }).getAST()[0].value.body.body; }; - const pickReturnValueOrCreateIIFE = value => { - if (hasSingleReturnStatementWithObject(value)) { - return value.body.body[0].argument; - } else { - return j.callExpression( - value, - [] - ); - } - }; - const convertInitialStateToClassProperty = getInitialState => withComments(j.classProperty( j.identifier('state'), @@ -853,27 +874,6 @@ module.exports = (file, api, options) => { const createStaticClassProperties = statics => statics.map(createStaticClassProperty); - const hasSingleReturnStatementWithObject = 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 createDefaultProps = prop => - withComments( - j.property( - 'init', - j.identifier(DEFAULT_PROPS_KEY), - pickReturnValueOrCreateIIFE(prop.value) - ), - prop - ); - const getComments = classPath => { if (classPath.value.comments) { return classPath.value.comments; diff --git a/transforms/sort-comp.js b/transforms/sort-comp.js index 97af1ac4..d37eee01 100644 --- a/transforms/sort-comp.js +++ b/transforms/sort-comp.js @@ -36,7 +36,7 @@ module.exports = function(fileInfo, api, options) { const printOptions = options.printOptions || {quote: 'single', trailingComma: true}; - const methodsOrder = getMethodsOrder(fileInfo, options); + const methodsOrder = getMethodsOrder(fileInfo, options); // eslint-disable-line no-use-before-define const root = j(fileInfo.source); @@ -44,8 +44,8 @@ module.exports = function(fileInfo, api, options) { const nameA = a.key.name; const nameB = b.key.name; - const indexA = getCorrectIndex(methodsOrder, a); - const indexB = getCorrectIndex(methodsOrder, b); + const indexA = getCorrectIndex(methodsOrder, a); // eslint-disable-line no-use-before-define + const indexB = getCorrectIndex(methodsOrder, b); // eslint-disable-line no-use-before-define const sameLocation = indexA === indexB; diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index dae4d4bb..9c80cfbc 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -86,6 +86,18 @@ module.exports = function(j) { }, }); + const getReactCreateClassSpec = classPath => { + const {value} = classPath; + const args = (value.init || value.right || value.declaration).arguments; + if (args) { + const spec = args[0]; + if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { + return spec; + } + } + return null; + }; + // --------------------------------------------------------------------------- // Finds alias for React.Component if used as named import. const findReactComponentName = path => { @@ -181,18 +193,6 @@ module.exports = function(j) { // --------------------------------------------------------------------------- // Others - const getReactCreateClassSpec = classPath => { - const {value} = classPath; - const args = (value.init || value.right || value.declaration).arguments; - if (args) { - const spec = args[0]; - if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { - return spec; - } - } - return null; - }; - const getClassExtendReactSpec = classPath => classPath.value.body; const createCreateReactClassCallExpression = properties => From 53730d93b2585e4f5c99940a7065263d79940b93 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 24 Jun 2016 02:05:17 -0700 Subject: [PATCH 33/64] displayName shouldn't show up twice --- transforms/__testfixtures__/class.input.js | 7 +++++++ transforms/__testfixtures__/class.output.js | 8 ++++++++ transforms/class.js | 12 +++--------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 37cfcc3b..7402be9a 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -152,3 +152,10 @@ var MyComponent5 = React.createClass({ ); }, }); + +var GoodName = React.createClass({ + displayName: 'GoodName', + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 9dce43f0..7c71486e 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -146,3 +146,11 @@ class MyComponent5 extends React.Component { ); } } + +class GoodName extends React.Component { + static displayName = 'GoodName'; + + render() { + return
; + } +} diff --git a/transforms/class.js b/transforms/class.js index 3ed60f3a..84a0021c 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -344,10 +344,11 @@ module.exports = (file, api, options) => { return result; }; - const collectProperties = specPath => specPath.properties + const collectNonStaticProperties = specPath => specPath.properties .filter(prop => !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop)) ) + .filter(prop => (!STATIC_KEYS[prop.key.name]) && prop.key.name !== STATIC_KEY) .filter(prop => isFunctionExpression(prop) || isPrimPropertyWithTypeAnnotation(prop) || @@ -782,13 +783,6 @@ module.exports = (file, api, options) => { // 1. there's a `this.context` access, or // 2. there's a direct method call `this.x()`, or // 3. `this` is referenced alone - // - // It creates a class with the following order of properties/methods: - // 1. static properties - // 2. constructor (if necessary) - // 3. new properties (`state = {...};`) - // 4. arrow functions - // 5. other methods const createESClass = ( name, baseClassName, @@ -908,7 +902,7 @@ module.exports = (file, api, options) => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); const name = ReactUtils.getComponentName(classPath); const statics = collectStatics(specPath); - const properties = collectProperties(specPath); + const properties = collectNonStaticProperties(specPath); const comments = getComments(classPath); const getInitialState = findGetInitialState(specPath); From 19b611aed2b596d38227c1f6dbc51ed3a1a0c344 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 24 Jun 2016 14:19:28 -0700 Subject: [PATCH 34/64] fixed anonymous createClass --- .../__testfixtures__/class-anonymous.input.js | 21 +++++++++++++++++++ .../class-anonymous.output.js | 21 +++++++++++++++++++ transforms/__tests__/class-test.js | 19 +++++++++-------- transforms/class.js | 8 ++++++- transforms/utils/ReactUtils.js | 16 ++++++++++++-- 5 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 transforms/__testfixtures__/class-anonymous.input.js create mode 100644 transforms/__testfixtures__/class-anonymous.output.js diff --git a/transforms/__testfixtures__/class-anonymous.input.js b/transforms/__testfixtures__/class-anonymous.input.js new file mode 100644 index 00000000..22b4ca7b --- /dev/null +++ b/transforms/__testfixtures__/class-anonymous.input.js @@ -0,0 +1,21 @@ +var React = require('react'); + +const wrapper = (x) => x; + +const Foo = wrapper(React.createClass({ + render() { + return
wow so anonymous
; + }, +})); + +module.exports = wrapper(React.createClass({ + render() { + return
wow so anonymous
; + }, +})); + +export default wrapper(React.createClass({ + render() { + return
wow so anonymous
; + }, +})); diff --git a/transforms/__testfixtures__/class-anonymous.output.js b/transforms/__testfixtures__/class-anonymous.output.js new file mode 100644 index 00000000..c90ecbc4 --- /dev/null +++ b/transforms/__testfixtures__/class-anonymous.output.js @@ -0,0 +1,21 @@ +var React = require('react'); + +const wrapper = (x) => x; + +const Foo = wrapper(class extends React.Component { + render() { + return
wow so anonymous
; + } +}); + +module.exports = wrapper(class extends React.Component { + render() { + return
wow so anonymous
; + } +}); + +export default wrapper(class extends React.Component { + render() { + return
wow so anonymous
; + } +}); diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index b66965c5..291b870f 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -17,14 +17,15 @@ const pureMixinAlternativeOption = { }; defineTest(__dirname, 'class'); +defineTest(__dirname, 'class', {flow: true}, 'class-anonymous'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); -defineTest(__dirname, 'class', { flow: true }, 'export-default-class'); +defineTest(__dirname, 'class', {flow: true}, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); -defineTest(__dirname, 'class', { flow: true }, 'class-pure-mixin2'); -defineTest(__dirname, 'class', { flow: true }, 'class-initial-state'); -defineTest(__dirname, 'class', { flow: true }, 'class-property-field'); -defineTest(__dirname, 'class', { flow: true }, 'class-flow1'); -defineTest(__dirname, 'class', { flow: true }, 'class-flow2'); -defineTest(__dirname, 'class', { flow: true }, 'class-flow3'); -defineTest(__dirname, 'class', { flow: true }, 'class-flow4'); -defineTest(__dirname, 'class', { flow: true }, 'class-flow5'); +defineTest(__dirname, 'class', {flow: true}, 'class-pure-mixin2'); +defineTest(__dirname, 'class', {flow: true}, 'class-initial-state'); +defineTest(__dirname, 'class', {flow: true}, 'class-property-field'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow1'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow2'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow3'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow4'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow5'); diff --git a/transforms/class.js b/transforms/class.js index 84a0021c..4066f299 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -908,7 +908,11 @@ module.exports = (file, api, options) => { const getInitialState = findGetInitialState(specPath); var path; - if (type == 'moduleExports' || type == 'exportDefault') { + if ( + type == 'moduleExports' || + type == 'exportDefault' || + type == 'anonymousInCallExpression' + ) { path = ReactUtils.findReactCreateClassCallExpression(classPath); } else { path = j(classPath).closest(j.VariableDeclaration); @@ -968,6 +972,8 @@ module.exports = (file, api, options) => { .forEach(classPath => updateToClass(classPath, type)); const didTransform = ( + apply(ReactUtils.findReactAnonymousCreateClassInCallExpression(root), 'anonymousInCallExpression') + .size() + apply(ReactUtils.findReactCreateClass(root), 'var') .size() + apply(ReactUtils.findReactCreateClassModuleExports(root), 'moduleExports') diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index 9c80cfbc..29d92c54 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -86,9 +86,20 @@ module.exports = function(j) { }, }); + const findReactAnonymousCreateClassInCallExpression = path => + path.find(j.CallExpression, { + arguments: [{ + type: 'CallExpression', + callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, + }], + }); + const getReactCreateClassSpec = classPath => { - const {value} = classPath; - const args = (value.init || value.right || value.declaration).arguments; + var callCollection = findReactCreateClassCallExpression(classPath); + if (callCollection.size() === 0) { + return null; + } + const args = callCollection.get('arguments').value; if (args) { const spec = args[0]; if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { @@ -210,6 +221,7 @@ module.exports = function(j) { return { createCreateReactClassCallExpression, + findReactAnonymousCreateClassInCallExpression, findReactES6ClassDeclaration, findReactCreateClass, findReactCreateClassCallExpression, From b86a89d828903c4ab007899c3826acdedbc12071 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 24 Jun 2016 16:34:57 -0700 Subject: [PATCH 35/64] covered more flow edge cases --- .../__testfixtures__/class-flow6.input.js | 39 +++++++++++++ .../__testfixtures__/class-flow6.output.js | 58 +++++++++++++++++++ transforms/__tests__/class-test.js | 1 + transforms/class.js | 40 ++++++++----- 4 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 transforms/__testfixtures__/class-flow6.input.js create mode 100644 transforms/__testfixtures__/class-flow6.output.js diff --git a/transforms/__testfixtures__/class-flow6.input.js b/transforms/__testfixtures__/class-flow6.input.js new file mode 100644 index 00000000..3c4dbcac --- /dev/null +++ b/transforms/__testfixtures__/class-flow6.input.js @@ -0,0 +1,39 @@ +/* @flow */ + +var React = require('react'); + +const justNeedKeys = { + a: 12, + b: 23, +}; + +var Component = React.createClass({ + propTypes: { + optionalMessage: React.PropTypes.instanceOf(Message), + optionalMessageOops: React.PropTypes.instanceOf(foo()), + optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), + optionalEnumOops: React.PropTypes.oneOf(bar), + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]), + optionalUnionOops: React.PropTypes.oneOfType(foo()), + optionalUnionOops2: React.PropTypes.oneOfType(Bar), + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: foo, + name: bla(), + }), + optionalObjectWithShapeOops: React.PropTypes.shape(foo()), + optionalObjectWithShapeOops2: React.PropTypes.shape(bla), + }, + + render: function() { + return ( +
type safety
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js new file mode 100644 index 00000000..6403cf7d --- /dev/null +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -0,0 +1,58 @@ +/* @flow */ + +var React = require('react'); + +const justNeedKeys = { + a: 12, + b: 23, +}; + +class Component extends React.Component { + props: { + optionalMessage?: Message, + optionalMessageOops?: any, + optionalEnum?: any, + optionalEnumOops?: any, + optionalUnion?: string | number | Message, + optionalUnionOops?: any, + optionalUnionOops2?: any, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number,}, + optionalObjectWithShape?: { + color?: string, + fontSize?: any, + name?: any, + }, + optionalObjectWithShapeOops?: any, + optionalObjectWithShapeOops2?: any, + }; + + static propTypes = { + optionalMessage: React.PropTypes.instanceOf(Message), + optionalMessageOops: React.PropTypes.instanceOf(foo()), + optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), + optionalEnumOops: React.PropTypes.oneOf(bar), + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]), + optionalUnionOops: React.PropTypes.oneOfType(foo()), + optionalUnionOops2: React.PropTypes.oneOfType(Bar), + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: foo, + name: bla(), + }), + optionalObjectWithShapeOops: React.PropTypes.shape(foo()), + optionalObjectWithShapeOops2: React.PropTypes.shape(bla), + }; + + render() { + return ( +
type safety
+ ); + } +} diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 291b870f..6f5046db 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -29,3 +29,4 @@ defineTest(__dirname, 'class', {flow: true}, 'class-flow2'); defineTest(__dirname, 'class', {flow: true}, 'class-flow3'); defineTest(__dirname, 'class', {flow: true}, 'class-flow4'); defineTest(__dirname, 'class', {flow: true}, 'class-flow5'); +defineTest(__dirname, 'class', {flow: true}, 'class-flow6'); diff --git a/transforms/class.js b/transforms/class.js index 4066f299..b8a3d156 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -684,7 +684,7 @@ module.exports = (file, api, options) => { } case 'oneOf': { const argList = cursor.arguments[0].elements; - if (!argList.every(node => node.type === 'Literal')) { + if (!argList || !argList.every(node => node.type === 'Literal')) { typeResult = flowAnyType; } else { typeResult = constructor( @@ -695,25 +695,33 @@ module.exports = (file, api, options) => { } case 'oneOfType': { const argList = cursor.arguments[0].elements; - typeResult = constructor( - argList.map(arg => propTypeToFlowAnnotation(arg)[0]) - ); + if (!argList) { + typeResult = flowAnyType; + } else { + typeResult = constructor( + argList.map(arg => propTypeToFlowAnnotation(arg)[0]) + ); + } break; } case 'shape': { const rawPropList = cursor.arguments[0].properties; - const flowPropList = []; - rawPropList.forEach(typeProp => { - const name = typeProp.key.name; - const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); - flowPropList.push(j.objectTypeProperty( - j.identifier(name), - valueType, - isOptional - )); - }); - - typeResult = constructor(flowPropList); + if (!rawPropList) { + typeResult = flowAnyType; + } else { + const flowPropList = []; + rawPropList.forEach(typeProp => { + const name = typeProp.key.name; + const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); + flowPropList.push(j.objectTypeProperty( + j.identifier(name), + valueType, + isOptional + )); + }); + + typeResult = constructor(flowPropList); + } break; } } From 595bb971682efa1949994965ec708515c9751e7a Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 24 Jun 2016 21:27:39 -0700 Subject: [PATCH 36/64] handle nullable prop types correctly --- npm-shrinkwrap.json | 2 +- .../__testfixtures__/class-flow1.input.js | 5 ++- .../__testfixtures__/class-flow1.output.js | 36 +++++++++++-------- .../__testfixtures__/class-flow3.output.js | 16 ++++----- .../__testfixtures__/class-flow6.output.js | 12 +++---- transforms/class.js | 23 ++++++++---- 6 files changed, 56 insertions(+), 38 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f42f9d46..2b15e396 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4,7 +4,7 @@ "version": "0.3.25", "dependencies": { "recast": { - "version": "git://github.com/keyanzhang/recast.git#temporary-codemod-fork" + "version": "https://github.com/keyanzhang/recast" } } } diff --git a/transforms/__testfixtures__/class-flow1.input.js b/transforms/__testfixtures__/class-flow1.input.js index 145fb2ba..de673c86 100644 --- a/transforms/__testfixtures__/class-flow1.input.js +++ b/transforms/__testfixtures__/class-flow1.input.js @@ -21,9 +21,12 @@ var Component = React.createClass({ ]), optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), + objectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, + objectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, - fontSize: React.PropTypes.number, + fontSize: React.PropTypes.number.isRequired, }), requiredFunc: React.PropTypes.func.isRequired, requiredAny: React.PropTypes.any.isRequired, diff --git a/transforms/__testfixtures__/class-flow1.output.js b/transforms/__testfixtures__/class-flow1.output.js index bb283e14..3a87f450 100644 --- a/transforms/__testfixtures__/class-flow1.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -4,22 +4,25 @@ var React = require('react'); class Component extends React.Component { props: { - optionalArray?: Array, - optionalBool?: boolean, - optionalFunc?: Function, - optionalNumber?: number, - optionalObject?: Object, - optionalString?: string, + optionalArray?: ?Array, + optionalBool?: ?boolean, + optionalFunc?: ?Function, + optionalNumber?: ?number, + optionalObject?: ?Object, + optionalString?: ?string, optionalNode?: any, optionalElement?: any, - optionalMessage?: Message, - optionalEnum?: 'News' | 'Photos' | 1 | true | null, - optionalUnion?: string | number | Message, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number,}, - optionalObjectWithShape?: { - color?: string, - fontSize?: number, + optionalMessage?: ?Message, + optionalEnum?: ?('News' | 'Photos' | 1 | true | null), + optionalUnion?: ?(string | number | Message), + optionalArrayOf?: ?Array, + optionalObjectOf?: ?{[key: string]: ?number,}, + optionalObjectOfNonOptionalField?: ?{[key: string]: number,}, + objectOfNonOptionalField: {[key: string]: number,}, + objectOfOptionalField: {[key: string]: ?number,}, + optionalObjectWithShape?: ?{ + color?: ?string, + fontSize: number, }, requiredFunc: Function, requiredAny: any, @@ -43,9 +46,12 @@ class Component extends React.Component { ]), optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), + objectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, + objectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, - fontSize: React.PropTypes.number, + fontSize: React.PropTypes.number.isRequired, }), requiredFunc: React.PropTypes.func.isRequired, requiredAny: React.PropTypes.any.isRequired, diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js index d8ca839a..542389d8 100644 --- a/transforms/__testfixtures__/class-flow3.output.js +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -22,18 +22,18 @@ class Component extends React.Component { props: { optionalFuncShortHand?: any, optionalNumber?: any, - optionalObject?: Object, + optionalObject?: ?Object, optionalString?: any, optionalNode?: any, optionalElement?: any, - optionalMessage?: Message, - optionalEnum?: 'News' | 'Photos' | 1 | true | null, + optionalMessage?: ?Message, + optionalEnum?: ?('News' | 'Photos' | 1 | true | null), optionalUnion?: any, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number,}, - optionalObjectWithShape?: { - color?: string, - fontSize?: number, + optionalArrayOf?: ?Array, + optionalObjectOf?: ?{[key: string]: ?number,}, + optionalObjectWithShape?: ?{ + color?: ?string, + fontSize?: ?number, }, requiredFunc: Function, requiredAny: any, diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js index 6403cf7d..dac4f258 100644 --- a/transforms/__testfixtures__/class-flow6.output.js +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -9,17 +9,17 @@ const justNeedKeys = { class Component extends React.Component { props: { - optionalMessage?: Message, + optionalMessage?: ?Message, optionalMessageOops?: any, optionalEnum?: any, optionalEnumOops?: any, - optionalUnion?: string | number | Message, + optionalUnion?: ?(string | number | Message), optionalUnionOops?: any, optionalUnionOops2?: any, - optionalArrayOf?: Array, - optionalObjectOf?: {[key: string]: number,}, - optionalObjectWithShape?: { - color?: string, + optionalArrayOf?: ?Array, + optionalObjectOf?: ?{[key: string]: ?number,}, + optionalObjectWithShape?: ?{ + color?: ?string, fontSize?: any, name?: any, }, diff --git a/transforms/class.js b/transforms/class.js index b8a3d156..bd01214d 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -624,8 +624,14 @@ module.exports = (file, api, options) => { type, null ), - objectOf: (type) => j.objectTypeAnnotation([], [ - j.objectTypeIndexer(j.identifier('key'), j.stringTypeAnnotation(), type) + objectOf: (type, isOptional) => j.objectTypeAnnotation([], [ + j.objectTypeIndexer( + j.identifier('key'), + j.stringTypeAnnotation(), + isOptional && type !== flowAnyType ? + j.nullableTypeAnnotation(type) : + type + ) ]), oneOf: (typeList) => j.unionTypeAnnotation(typeList), oneOfType: (typeList) => j.unionTypeAnnotation(typeList), @@ -677,9 +683,8 @@ module.exports = (file, api, options) => { } case 'objectOf': { const arg = cursor.arguments[0]; - typeResult = constructor( - propTypeToFlowAnnotation(arg)[0] - ); + const [valueType, isOptional] = propTypeToFlowAnnotation(arg); + typeResult = constructor(valueType, isOptional); break; } case 'oneOf': { @@ -715,7 +720,9 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); flowPropList.push(j.objectTypeProperty( j.identifier(name), - valueType, + isOptional && valueType !== flowAnyType ? + j.nullableTypeAnnotation(valueType) : + valueType, isOptional )); }); @@ -751,7 +758,9 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); typePropertyList.push(j.objectTypeProperty( j.identifier(name), - valueType, + isOptional && valueType !== flowAnyType ? + j.nullableTypeAnnotation(valueType) : + valueType, isOptional )); }); From 5b058f869397ae86364f571bc4b06d8658844015 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 27 Jun 2016 11:33:30 -0700 Subject: [PATCH 37/64] always print parens for single arg arrow functions --- transforms/__testfixtures__/class.input.js | 9 +++++++++ transforms/__testfixtures__/class.output.js | 10 ++++++++++ transforms/class.js | 8 +++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 7402be9a..bf7d72f7 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -159,3 +159,12 @@ var GoodName = React.createClass({ return
; }, }); + +var SingleArgArrowFunction = React.createClass({ + formatInt: function(/*number*/ num) /*string*/ { + return 'foobar'; + }, + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 7c71486e..1c388845 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -154,3 +154,13 @@ class GoodName extends React.Component { return
; } } + +class SingleArgArrowFunction extends React.Component { + formatInt = (/*number*/ num) => /*string*/ { + return 'foobar'; + }; + + render() { + return
; + } +} diff --git a/transforms/class.js b/transforms/class.js index bd01214d..d0daaff0 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -17,7 +17,13 @@ module.exports = (file, api, options) => { const ReactUtils = require('./utils/ReactUtils')(j); const printOptions = - options.printOptions || {quote: 'single', trailingComma: true}; + options.printOptions || { + quote: 'single', + trailingComma: true, + flowUsesCommas: true, + arrowParensAlways: true, + }; + const root = j(file.source); const AUTOBIND_IGNORE_KEYS = { From b532b5261a266e89918b37ec3c44db0ab4a053df Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 27 Jun 2016 14:37:26 -0700 Subject: [PATCH 38/64] fixed edge case when class spec is not an object expression --- transforms/__testfixtures__/class.input.js | 3 +++ transforms/__testfixtures__/class.output.js | 3 +++ transforms/class.js | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index bf7d72f7..05aea38e 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -168,3 +168,6 @@ var SingleArgArrowFunction = React.createClass({ return
; }, }); + +var mySpec = {}; +var NotAnObjectLiteral = React.createClass(mySpec); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 1c388845..20624e42 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -164,3 +164,6 @@ class SingleArgArrowFunction extends React.Component { return
; } } + +var mySpec = {}; +var NotAnObjectLiteral = React.createClass(mySpec); diff --git a/transforms/class.js b/transforms/class.js index d0daaff0..f2e2a604 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -23,7 +23,7 @@ module.exports = (file, api, options) => { flowUsesCommas: true, arrowParensAlways: true, }; - + const root = j(file.source); const AUTOBIND_IGNORE_KEYS = { @@ -269,6 +269,9 @@ module.exports = (file, api, options) => { const canConvertToClass = classPath => { const specPath = ReactUtils.getReactCreateClassSpec(classPath); + if (!specPath) { + return false; + } const invalidProperties = specPath.properties.filter(prop => ( !prop.key.name || ( !STATIC_KEYS[prop.key.name] && From da84fafdaca6911263548aa2bb291fef1f2cd3fc Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 27 Jun 2016 16:56:26 -0700 Subject: [PATCH 39/64] support literal keys in prop types --- transforms/__testfixtures__/class-flow6.input.js | 2 ++ transforms/__testfixtures__/class-flow6.output.js | 4 ++++ transforms/class.js | 12 ++++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/transforms/__testfixtures__/class-flow6.input.js b/transforms/__testfixtures__/class-flow6.input.js index 3c4dbcac..b008f814 100644 --- a/transforms/__testfixtures__/class-flow6.input.js +++ b/transforms/__testfixtures__/class-flow6.input.js @@ -29,6 +29,8 @@ var Component = React.createClass({ }), optionalObjectWithShapeOops: React.PropTypes.shape(foo()), optionalObjectWithShapeOops2: React.PropTypes.shape(bla), + 'is-literal-cool': React.PropTypes.bool, + 'well-fine': React.PropTypes.number.isRequired, }, render: function() { diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js index dac4f258..efc2c443 100644 --- a/transforms/__testfixtures__/class-flow6.output.js +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -25,6 +25,8 @@ class Component extends React.Component { }, optionalObjectWithShapeOops?: any, optionalObjectWithShapeOops2?: any, + 'is-literal-cool'?: ?boolean, + 'well-fine': number, }; static propTypes = { @@ -48,6 +50,8 @@ class Component extends React.Component { }), optionalObjectWithShapeOops: React.PropTypes.shape(foo()), optionalObjectWithShapeOops2: React.PropTypes.shape(bla), + 'is-literal-cool': React.PropTypes.bool, + 'well-fine': React.PropTypes.number.isRequired, }; render() { diff --git a/transforms/class.js b/transforms/class.js index f2e2a604..be7edfad 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -725,10 +725,12 @@ module.exports = (file, api, options) => { } else { const flowPropList = []; rawPropList.forEach(typeProp => { - const name = typeProp.key.name; + const keyIsLiteral = typeProp.key.type === 'Literal'; + const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name; + const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); flowPropList.push(j.objectTypeProperty( - j.identifier(name), + keyIsLiteral ? j.literal(name) : j.identifier(name), isOptional && valueType !== flowAnyType ? j.nullableTypeAnnotation(valueType) : valueType, @@ -763,10 +765,12 @@ module.exports = (file, api, options) => { return; } - const name = typeProp.key.name; + const keyIsLiteral = typeProp.key.type === 'Literal'; + const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name; + const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); typePropertyList.push(j.objectTypeProperty( - j.identifier(name), + keyIsLiteral ? j.literal(name) : j.identifier(name), isOptional && valueType !== flowAnyType ? j.nullableTypeAnnotation(valueType) : valueType, From 7214e92e6e189877639f7ec08bf6a5873de2b451 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Mon, 27 Jun 2016 19:23:12 -0700 Subject: [PATCH 40/64] added TypeParameter and NullTypeAnnotation to ast-types --- transforms/__testfixtures__/class-flow5.input.js | 5 +++++ transforms/__testfixtures__/class-flow5.output.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/transforms/__testfixtures__/class-flow5.input.js b/transforms/__testfixtures__/class-flow5.input.js index ddab97c5..3abae359 100644 --- a/transforms/__testfixtures__/class-flow5.input.js +++ b/transforms/__testfixtures__/class-flow5.input.js @@ -2,9 +2,14 @@ var React = require('react'); +type SomeStuff = { // TypeParameter + fetch: () => Promise, +}; + var Component = React.createClass({ statics: { notTyped: true, + nothing: (null: null), // NullTypeAnnotation numberOrBool: (true: number | boolean), logger: (x: any): void => { console.log(x); }, logger2: function(x: any): void { diff --git a/transforms/__testfixtures__/class-flow5.output.js b/transforms/__testfixtures__/class-flow5.output.js index 5b3faac1..ca001120 100644 --- a/transforms/__testfixtures__/class-flow5.output.js +++ b/transforms/__testfixtures__/class-flow5.output.js @@ -2,8 +2,13 @@ var React = require('react'); +type SomeStuff = { // TypeParameter + fetch: () => Promise, +}; + class Component extends React.Component { static notTyped = true; + static nothing: null = null; // NullTypeAnnotation static numberOrBool: number | boolean = true; static logger = (x: any): void => { console.log(x); }; From bb15c72ca53a8c9db578d9867e982c59670e0941 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 28 Jun 2016 14:27:29 -0700 Subject: [PATCH 41/64] renamed recast option to flowObjectCommas --- transforms/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/class.js b/transforms/class.js index be7edfad..43565cd7 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -20,7 +20,7 @@ module.exports = (file, api, options) => { options.printOptions || { quote: 'single', trailingComma: true, - flowUsesCommas: true, + flowObjectCommas: true, arrowParensAlways: true, }; From 9f1702b4ef9acae4a6307e623ccc57410d86ebb5 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 28 Jun 2016 15:04:32 -0700 Subject: [PATCH 42/64] no trailing comma for single-line flow object types --- transforms/__testfixtures__/class-flow1.output.js | 8 ++++---- transforms/__testfixtures__/class-flow3.input.js | 1 - transforms/__testfixtures__/class-flow3.output.js | 8 ++------ transforms/__testfixtures__/class-flow6.output.js | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/transforms/__testfixtures__/class-flow1.output.js b/transforms/__testfixtures__/class-flow1.output.js index 3a87f450..658eddb1 100644 --- a/transforms/__testfixtures__/class-flow1.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -16,10 +16,10 @@ class Component extends React.Component { optionalEnum?: ?('News' | 'Photos' | 1 | true | null), optionalUnion?: ?(string | number | Message), optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number,}, - optionalObjectOfNonOptionalField?: ?{[key: string]: number,}, - objectOfNonOptionalField: {[key: string]: number,}, - objectOfOptionalField: {[key: string]: ?number,}, + optionalObjectOf?: ?{[key: string]: ?number}, + optionalObjectOfNonOptionalField?: ?{[key: string]: number}, + objectOfNonOptionalField: {[key: string]: number}, + objectOfOptionalField: {[key: string]: ?number}, optionalObjectWithShape?: ?{ color?: ?string, fontSize: number, diff --git a/transforms/__testfixtures__/class-flow3.input.js b/transforms/__testfixtures__/class-flow3.input.js index cb19b550..f42dfd44 100644 --- a/transforms/__testfixtures__/class-flow3.input.js +++ b/transforms/__testfixtures__/class-flow3.input.js @@ -34,7 +34,6 @@ var Component = React.createClass({ optionalObjectOf: PropTypes.objectOf(PropTypes.number), optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, - fontSize: PropTypes.number, }), requiredFunc: PropTypes.func.isRequired, requiredAny: PropTypes.any.isRequired, diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js index 542389d8..f6c0a62d 100644 --- a/transforms/__testfixtures__/class-flow3.output.js +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -30,11 +30,8 @@ class Component extends React.Component { optionalEnum?: ?('News' | 'Photos' | 1 | true | null), optionalUnion?: any, optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number,}, - optionalObjectWithShape?: ?{ - color?: ?string, - fontSize?: ?number, - }, + optionalObjectOf?: ?{[key: string]: ?number}, + optionalObjectWithShape?: ?{color?: ?string}, requiredFunc: Function, requiredAny: any, }; @@ -54,7 +51,6 @@ class Component extends React.Component { optionalObjectOf: PropTypes.objectOf(PropTypes.number), optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, - fontSize: PropTypes.number, }), requiredFunc: PropTypes.func.isRequired, requiredAny: PropTypes.any.isRequired, diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js index efc2c443..2cdd09d3 100644 --- a/transforms/__testfixtures__/class-flow6.output.js +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -17,7 +17,7 @@ class Component extends React.Component { optionalUnionOops?: any, optionalUnionOops2?: any, optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number,}, + optionalObjectOf?: ?{[key: string]: ?number}, optionalObjectWithShape?: ?{ color?: ?string, fontSize?: any, From d7083fe284d29f4732a5e496f4abc78ca38a47a9 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 28 Jun 2016 20:00:42 -0700 Subject: [PATCH 43/64] catch edge case where React.createClass() is called with nothing --- transforms/__testfixtures__/class.input.js | 2 ++ transforms/__testfixtures__/class.output.js | 2 ++ transforms/utils/ReactUtils.js | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index 05aea38e..bbb73b28 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -171,3 +171,5 @@ var SingleArgArrowFunction = React.createClass({ var mySpec = {}; var NotAnObjectLiteral = React.createClass(mySpec); + +var WaitWhat = React.createClass(); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 20624e42..2ac5482e 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -167,3 +167,5 @@ class SingleArgArrowFunction extends React.Component { var mySpec = {}; var NotAnObjectLiteral = React.createClass(mySpec); + +var WaitWhat = React.createClass(); diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index 29d92c54..53498533 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -102,7 +102,7 @@ module.exports = function(j) { const args = callCollection.get('arguments').value; if (args) { const spec = args[0]; - if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { + if (spec && spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { return spec; } } From ef23055de5e85eb98feb0164010f3585d106c87e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 28 Jun 2016 20:11:16 -0700 Subject: [PATCH 44/64] improved the logic of repositioning `state` property --- .../class-initial-state.input.js | 13 +++++++++++ .../class-initial-state.output.js | 11 ++++++++++ transforms/class.js | 22 ++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index fbfe41a5..12f300dc 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -107,6 +107,19 @@ var MyComponent4 = React.createClass({ }, }); +// but only accessing `this.props` and/or `this.context` is safe +var MyComponent5 = React.createClass({ + getInitialState: function() { + return { + heyoo: getContextFromInstance(this.props), + }; + }, + + foo: function(): void { + this.setState({heyoo: 24}); + }, +}); + // intense control flow testing var Loader = React.createClass({ getInitialState() { diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 07a8bd49..1523cf76 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -114,6 +114,17 @@ class MyComponent4 extends React.Component { }; } +// but only accessing `this.props` and/or `this.context` is safe +class MyComponent5 extends React.Component { + state = { + heyoo: getContextFromInstance(this.props), + }; + + foo = (): void => { + this.setState({heyoo: 24}); + }; +} + // intense control flow testing class Loader extends React.Component { constructor(props, context) { diff --git a/transforms/class.js b/transforms/class.js index 43565cd7..126e2d62 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -788,7 +788,27 @@ module.exports = (file, api, options) => { // to ensure that our property initializers' evaluation order is safe const repositionStateProperty = (initialStateProperty, propertiesAndMethods) => { - if (j(initialStateProperty).find(j.ThisExpression).size() === 0) { + const initialStateCollection = j(initialStateProperty); + const thisCount = initialStateCollection.find(j.ThisExpression).size(); + const safeThisMemberCount = initialStateCollection.find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'props', + }, + }).size() + initialStateCollection.find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'context', + }, + }).size(); + + if (thisCount === safeThisMemberCount) { return initialStateProperty.concat(propertiesAndMethods); } From 46d13c059e8a9bfb796f19e21d44115e0de24199 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 28 Jun 2016 20:18:55 -0700 Subject: [PATCH 45/64] added `pure-component` option --- .../class-pure-mixin3.input.js | 20 ++++++++++++++ .../class-pure-mixin3.output.js | 0 transforms/__tests__/class-test.js | 26 +++++++++++-------- transforms/class.js | 2 +- 4 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 transforms/__testfixtures__/class-pure-mixin3.input.js create mode 100644 transforms/__testfixtures__/class-pure-mixin3.output.js diff --git a/transforms/__testfixtures__/class-pure-mixin3.input.js b/transforms/__testfixtures__/class-pure-mixin3.input.js new file mode 100644 index 00000000..32592bf2 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin3.input.js @@ -0,0 +1,20 @@ +// for this file we disable the `pure-component` option +// so the output should be just nothing +var React = require('React'); +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); + +var ComponentWithOnlyPureRenderMixin = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-pure-mixin3.output.js b/transforms/__testfixtures__/class-pure-mixin3.output.js new file mode 100644 index 00000000..e69de29b diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 6f5046db..5d05cbea 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -14,19 +14,23 @@ const defineTest = require('jscodeshift/dist/testUtils').defineTest; const pureMixinAlternativeOption = { 'mixin-module-name': 'ReactComponentWithPureRenderMixin', + 'pure-component': true, }; +const enableFlowOption = {flow: true}; + defineTest(__dirname, 'class'); -defineTest(__dirname, 'class', {flow: true}, 'class-anonymous'); +defineTest(__dirname, 'class', enableFlowOption, 'class-anonymous'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); -defineTest(__dirname, 'class', {flow: true}, 'export-default-class'); +defineTest(__dirname, 'class', enableFlowOption, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); -defineTest(__dirname, 'class', {flow: true}, 'class-pure-mixin2'); -defineTest(__dirname, 'class', {flow: true}, 'class-initial-state'); -defineTest(__dirname, 'class', {flow: true}, 'class-property-field'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow1'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow2'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow3'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow4'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow5'); -defineTest(__dirname, 'class', {flow: true}, 'class-flow6'); +defineTest(__dirname, 'class', {...enableFlowOption, 'pure-component': true}, 'class-pure-mixin2'); +defineTest(__dirname, 'class', null, 'class-pure-mixin3'); +defineTest(__dirname, 'class', enableFlowOption, 'class-initial-state'); +defineTest(__dirname, 'class', enableFlowOption, 'class-property-field'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow1'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow2'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow3'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow4'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow5'); +defineTest(__dirname, 'class', enableFlowOption, 'class-flow6'); diff --git a/transforms/class.js b/transforms/class.js index 126e2d62..0d1e5cb4 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -998,7 +998,7 @@ module.exports = (file, api, options) => { const mixinsFilter = (classPath) => { if (!ReactUtils.hasMixins(classPath)) { return true; - } else if (pureRenderMixinPathAndBinding) { + } else if (options['pure-component'] && pureRenderMixinPathAndBinding) { const {binding} = pureRenderMixinPathAndBinding; if (areMixinsConvertible([binding], classPath)) { return true; From 82d4a5209f1c75e8f95a22ff89582e6ec726966c Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 29 Jun 2016 12:05:17 -0700 Subject: [PATCH 46/64] updated npm-shrinkwrap --- npm-shrinkwrap.json | 2977 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 2976 insertions(+), 1 deletion(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2b15e396..41f62b03 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,2987 @@ { + "name": "react-codemod", + "version": "4.0.0", "dependencies": { + "abab": { + "version": "1.0.3", + "from": "abab@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz" + }, + "abbrev": { + "version": "1.0.9", + "from": "abbrev@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" + }, + "acorn": { + "version": "3.2.0", + "from": "acorn@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.2.0.tgz" + }, + "acorn-globals": { + "version": "1.0.9", + "from": "acorn-globals@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "dependencies": { + "acorn": { + "version": "2.7.0", + "from": "acorn@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz" + } + } + }, + "acorn-jsx": { + "version": "3.0.1", + "from": "acorn-jsx@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz" + }, + "align-text": { + "version": "0.1.4", + "from": "align-text@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" + }, + "alter": { + "version": "0.2.0", + "from": "alter@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz" + }, + "amdefine": { + "version": "1.0.0", + "from": "amdefine@>=0.0.4", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" + }, + "ansi-escapes": { + "version": "1.4.0", + "from": "ansi-escapes@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz" + }, + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + }, + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, + "ansicolors": { + "version": "0.2.1", + "from": "ansicolors@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz" + }, + "argparse": { + "version": "1.0.7", + "from": "argparse@>=1.0.7 <2.0.0", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.7.tgz" + }, + "arr-diff": { + "version": "2.0.0", + "from": "arr-diff@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" + }, + "arr-flatten": { + "version": "1.0.1", + "from": "arr-flatten@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" + }, + "array-differ": { + "version": "1.0.0", + "from": "array-differ@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz" + }, + "array-equal": { + "version": "1.0.0", + "from": "array-equal@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz" + }, + "array-find-index": { + "version": "1.0.1", + "from": "array-find-index@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz" + }, + "array-union": { + "version": "1.0.2", + "from": "array-union@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz" + }, + "array-uniq": { + "version": "1.0.3", + "from": "array-uniq@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" + }, + "array-unique": { + "version": "0.2.1", + "from": "array-unique@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" + }, + "arrify": { + "version": "1.0.1", + "from": "arrify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "ast-traverse": { + "version": "0.1.1", + "from": "ast-traverse@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz" + }, + "ast-types": { + "version": "0.8.12", + "from": "ast-types@0.8.12", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz" + }, + "async": { + "version": "1.5.2", + "from": "async@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "aws4": { + "version": "1.4.1", + "from": "aws4@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" + }, + "babel-code-frame": { + "version": "6.11.0", + "from": "babel-code-frame@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.11.0.tgz" + }, + "babel-core": { + "version": "6.10.4", + "from": "babel-core@>=6.0.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.10.4.tgz" + }, + "babel-eslint": { + "version": "6.1.0", + "from": "babel-eslint@>=6.0.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-6.1.0.tgz" + }, + "babel-generator": { + "version": "6.11.0", + "from": "babel-generator@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.11.0.tgz" + }, + "babel-helper-call-delegate": { + "version": "6.8.0", + "from": "babel-helper-call-delegate@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.8.0.tgz" + }, + "babel-helper-define-map": { + "version": "6.9.0", + "from": "babel-helper-define-map@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.9.0.tgz" + }, + "babel-helper-function-name": { + "version": "6.8.0", + "from": "babel-helper-function-name@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.8.0.tgz" + }, + "babel-helper-get-function-arity": { + "version": "6.8.0", + "from": "babel-helper-get-function-arity@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.8.0.tgz" + }, + "babel-helper-hoist-variables": { + "version": "6.8.0", + "from": "babel-helper-hoist-variables@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.8.0.tgz" + }, + "babel-helper-optimise-call-expression": { + "version": "6.8.0", + "from": "babel-helper-optimise-call-expression@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.8.0.tgz" + }, + "babel-helper-regex": { + "version": "6.9.0", + "from": "babel-helper-regex@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.9.0.tgz" + }, + "babel-helper-replace-supers": { + "version": "6.8.0", + "from": "babel-helper-replace-supers@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.8.0.tgz" + }, + "babel-helpers": { + "version": "6.8.0", + "from": "babel-helpers@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.8.0.tgz" + }, + "babel-jest": { + "version": "13.0.0", + "from": "babel-jest@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-13.0.0.tgz" + }, + "babel-messages": { + "version": "6.8.0", + "from": "babel-messages@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.8.0.tgz" + }, + "babel-plugin-check-es2015-constants": { + "version": "6.8.0", + "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz" + }, + "babel-plugin-constant-folding": { + "version": "1.0.1", + "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" + }, + "babel-plugin-dead-code-elimination": { + "version": "1.0.2", + "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" + }, + "babel-plugin-eval": { + "version": "1.0.1", + "from": "babel-plugin-eval@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" + }, + "babel-plugin-inline-environment-variables": { + "version": "1.0.1", + "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" + }, + "babel-plugin-jest-hoist": { + "version": "13.0.0", + "from": "babel-plugin-jest-hoist@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-13.0.0.tgz" + }, + "babel-plugin-jscript": { + "version": "1.0.4", + "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" + }, + "babel-plugin-member-expression-literals": { + "version": "1.0.1", + "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" + }, + "babel-plugin-property-literals": { + "version": "1.0.1", + "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" + }, + "babel-plugin-proto-to-assign": { + "version": "1.0.4", + "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz", + "dependencies": { + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.9.3 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + } + } + }, + "babel-plugin-react-constant-elements": { + "version": "1.0.3", + "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" + }, + "babel-plugin-react-display-name": { + "version": "1.0.3", + "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" + }, + "babel-plugin-remove-console": { + "version": "1.0.1", + "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" + }, + "babel-plugin-remove-debugger": { + "version": "1.0.1", + "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" + }, + "babel-plugin-runtime": { + "version": "1.0.7", + "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" + }, + "babel-plugin-syntax-async-functions": { + "version": "6.8.0", + "from": "babel-plugin-syntax-async-functions@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.8.0.tgz" + }, + "babel-plugin-syntax-class-properties": { + "version": "6.8.0", + "from": "babel-plugin-syntax-class-properties@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.8.0.tgz" + }, + "babel-plugin-syntax-flow": { + "version": "6.8.0", + "from": "babel-plugin-syntax-flow@>=6.5.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.8.0.tgz" + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.8.0", + "from": "babel-plugin-syntax-object-rest-spread@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.8.0.tgz" + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.8.0", + "from": "babel-plugin-syntax-trailing-function-commas@>=6.5.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.8.0.tgz" + }, + "babel-plugin-transform-class-properties": { + "version": "6.10.2", + "from": "babel-plugin-transform-class-properties@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.10.2.tgz" + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-arrow-functions@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-block-scoped-functions@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.10.1", + "from": "babel-plugin-transform-es2015-block-scoping@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.10.1.tgz" + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.9.0", + "from": "babel-plugin-transform-es2015-classes@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.9.0.tgz" + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-computed-properties@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.9.0", + "from": "babel-plugin-transform-es2015-destructuring@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.9.0.tgz" + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-duplicate-keys@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-for-of@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.9.0", + "from": "babel-plugin-transform-es2015-function-name@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.9.0.tgz" + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-literals@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.10.3", + "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.10.3.tgz" + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-object-super@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.9.0", + "from": "babel-plugin-transform-es2015-parameters@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.9.0.tgz" + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-shorthand-properties@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-spread@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-sticky-regex@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-template-literals@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.8.0", + "from": "babel-plugin-transform-es2015-typeof-symbol@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.8.0.tgz" + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.11.0", + "from": "babel-plugin-transform-es2015-unicode-regex@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.11.0.tgz" + }, + "babel-plugin-transform-es3-member-expression-literals": { + "version": "6.8.0", + "from": "babel-plugin-transform-es3-member-expression-literals@>=6.5.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.8.0.tgz" + }, + "babel-plugin-transform-es3-property-literals": { + "version": "6.8.0", + "from": "babel-plugin-transform-es3-property-literals@>=6.5.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.8.0.tgz" + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.8.0", + "from": "babel-plugin-transform-flow-strip-types@>=6.7.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.8.0.tgz" + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.8.0", + "from": "babel-plugin-transform-object-rest-spread@>=6.6.5 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.8.0.tgz" + }, + "babel-plugin-transform-regenerator": { + "version": "6.9.0", + "from": "babel-plugin-transform-regenerator@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.9.0.tgz" + }, + "babel-plugin-transform-strict-mode": { + "version": "6.8.0", + "from": "babel-plugin-transform-strict-mode@>=6.8.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.8.0.tgz" + }, + "babel-plugin-undeclared-variables-check": { + "version": "1.0.2", + "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" + }, + "babel-plugin-undefined-to-void": { + "version": "1.1.6", + "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" + }, + "babel-preset-es2015": { + "version": "6.9.0", + "from": "babel-preset-es2015@>=6.6.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.9.0.tgz" + }, + "babel-preset-fbjs": { + "version": "1.0.0", + "from": "babel-preset-fbjs@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-1.0.0.tgz" + }, + "babel-preset-jest": { + "version": "13.0.0", + "from": "babel-preset-jest@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-13.0.0.tgz" + }, + "babel-register": { + "version": "6.9.0", + "from": "babel-register@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.9.0.tgz" + }, + "babel-runtime": { + "version": "6.9.2", + "from": "babel-runtime@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.9.2.tgz" + }, + "babel-template": { + "version": "6.9.0", + "from": "babel-template@>=6.9.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.9.0.tgz" + }, + "babel-traverse": { + "version": "6.10.4", + "from": "babel-traverse@>=6.0.20 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.10.4.tgz" + }, + "babel-types": { + "version": "6.11.1", + "from": "babel-types@>=6.0.19 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.11.1.tgz" + }, + "babylon": { + "version": "6.8.2", + "from": "babylon@>=6.0.18 <7.0.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.8.2.tgz" + }, + "balanced-match": { + "version": "0.4.1", + "from": "balanced-match@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz" + }, + "beeper": { + "version": "1.1.0", + "from": "beeper@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz" + }, + "bl": { + "version": "1.1.2", + "from": "bl@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz" + }, + "bluebird": { + "version": "3.4.1", + "from": "bluebird@>=3.1.1 <4.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.1.tgz" + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "brace-expansion": { + "version": "1.1.5", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.5.tgz" + }, + "braces": { + "version": "1.8.5", + "from": "braces@>=1.8.2 <2.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz" + }, + "breakable": { + "version": "1.0.0", + "from": "breakable@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz" + }, + "browser-resolve": { + "version": "1.11.2", + "from": "browser-resolve@>=1.11.2 <2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz" + }, + "bser": { + "version": "1.0.2", + "from": "bser@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz" + }, + "builtin-modules": { + "version": "1.1.1", + "from": "builtin-modules@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" + }, + "caller-path": { + "version": "0.1.0", + "from": "caller-path@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz" + }, + "callsites": { + "version": "0.2.0", + "from": "callsites@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz" + }, + "camelcase": { + "version": "2.1.1", + "from": "camelcase@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" + }, + "camelcase-keys": { + "version": "2.1.0", + "from": "camelcase-keys@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz" + }, + "cardinal": { + "version": "0.5.0", + "from": "cardinal@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.5.0.tgz" + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "center-align": { + "version": "0.1.3", + "from": "center-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz" + }, + "chalk": { + "version": "1.1.3", + "from": "chalk@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + }, + "cli-cursor": { + "version": "1.0.2", + "from": "cli-cursor@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz" + }, + "cli-table": { + "version": "0.3.1", + "from": "cli-table@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz" + }, + "cli-usage": { + "version": "0.1.2", + "from": "cli-usage@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.2.tgz", + "dependencies": { + "minimist": { + "version": "0.2.0", + "from": "minimist@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz" + } + } + }, + "cli-width": { + "version": "2.1.0", + "from": "cli-width@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz" + }, + "cliui": { + "version": "2.1.0", + "from": "cliui@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "from": "wordwrap@0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + } + } + }, + "clone": { + "version": "1.0.2", + "from": "clone@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" + }, + "clone-stats": { + "version": "0.0.1", + "from": "clone-stats@>=0.0.1 <0.0.2", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz" + }, + "code-point-at": { + "version": "1.0.0", + "from": "code-point-at@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz" + }, + "colors": { + "version": "1.0.3", + "from": "colors@1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "commander": { + "version": "2.9.0", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + }, + "commoner": { + "version": "0.10.4", + "from": "commoner@>=0.10.3 <0.11.0", + "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.4.tgz", + "dependencies": { + "glob": { + "version": "5.0.15", + "from": "glob@>=5.0.15 <6.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + } + } + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.5.1", + "from": "concat-stream@>=1.4.6 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz" + }, + "convert-source-map": { + "version": "1.2.0", + "from": "convert-source-map@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.2.0.tgz" + }, + "core-js": { + "version": "2.4.0", + "from": "core-js@>=2.4.0 <3.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.0.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "cross-spawn": { + "version": "3.0.1", + "from": "cross-spawn@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "cssom": { + "version": "0.3.1", + "from": "cssom@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.1.tgz" + }, + "cssstyle": { + "version": "0.2.36", + "from": "cssstyle@>=0.2.36 <0.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.36.tgz" + }, + "currently-unhandled": { + "version": "0.4.1", + "from": "currently-unhandled@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz" + }, + "d": { + "version": "0.1.1", + "from": "d@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" + }, + "dashdash": { + "version": "1.14.0", + "from": "dashdash@>=1.12.0 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "dateformat": { + "version": "1.0.12", + "from": "dateformat@>=1.0.11 <2.0.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz" + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "decamelize": { + "version": "1.2.0", + "from": "decamelize@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + }, + "deep-is": { + "version": "0.1.3", + "from": "deep-is@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" + }, + "defined": { + "version": "1.0.0", + "from": "defined@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz" + }, + "defs": { + "version": "1.1.1", + "from": "defs@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz", + "dependencies": { + "camelcase": { + "version": "1.2.1", + "from": "camelcase@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" + }, + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + }, + "window-size": { + "version": "0.1.4", + "from": "window-size@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz" + }, + "yargs": { + "version": "3.27.0", + "from": "yargs@>=3.27.0 <3.28.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz" + } + } + }, + "del": { + "version": "2.2.1", + "from": "del@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.1.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "detect-indent": { + "version": "3.0.1", + "from": "detect-indent@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz" + }, + "detective": { + "version": "4.3.1", + "from": "detective@>=4.3.1 <5.0.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.3.1.tgz", + "dependencies": { + "acorn": { + "version": "1.2.2", + "from": "acorn@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz" + } + } + }, + "diff": { + "version": "2.2.3", + "from": "diff@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz" + }, + "doctrine": { + "version": "1.2.2", + "from": "doctrine@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.2.2.tgz", + "dependencies": { + "esutils": { + "version": "1.1.6", + "from": "esutils@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz" + } + } + }, + "duplexer2": { + "version": "0.0.2", + "from": "duplexer2@0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "readable-stream": { + "version": "1.1.14", + "from": "readable-stream@>=1.1.9 <1.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" + } + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" + }, + "errno": { + "version": "0.1.4", + "from": "errno@>=0.1.1 <0.2.0-0", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz" + }, + "error-ex": { + "version": "1.3.0", + "from": "error-ex@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz" + }, + "es5-ext": { + "version": "0.10.11", + "from": "es5-ext@>=0.10.11 <0.11.0", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz", + "dependencies": { + "es6-symbol": { + "version": "3.0.2", + "from": "es6-symbol@>=3.0.2 <3.1.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz" + } + } + }, + "es6-iterator": { + "version": "2.0.0", + "from": "es6-iterator@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" + }, + "es6-map": { + "version": "0.1.4", + "from": "es6-map@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz" + }, + "es6-promise": { + "version": "3.2.1", + "from": "es6-promise@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz" + }, + "es6-set": { + "version": "0.1.4", + "from": "es6-set@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz" + }, + "es6-symbol": { + "version": "3.1.0", + "from": "es6-symbol@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz" + }, + "es6-weak-map": { + "version": "2.0.1", + "from": "es6-weak-map@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + }, + "escodegen": { + "version": "1.8.0", + "from": "escodegen@>=1.8.0 <1.9.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.0.tgz", + "dependencies": { + "estraverse": { + "version": "1.9.3", + "from": "estraverse@>=1.9.1 <2.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" + }, + "source-map": { + "version": "0.2.0", + "from": "source-map@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz" + } + } + }, + "escope": { + "version": "3.6.0", + "from": "escope@>=3.6.0 <4.0.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz" + }, + "eslint": { + "version": "2.13.1", + "from": "eslint@>=2.13.1 <3.0.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", + "dependencies": { + "globals": { + "version": "9.8.0", + "from": "globals@>=9.2.0 <10.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.8.0.tgz" + }, + "user-home": { + "version": "2.0.0", + "from": "user-home@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz" + } + } + }, + "eslint-plugin-react": { + "version": "5.2.2", + "from": "eslint-plugin-react@>=5.2.2 <6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-5.2.2.tgz" + }, + "espree": { + "version": "3.1.6", + "from": "espree@>=3.1.6 <4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.1.6.tgz" + }, + "esprima": { + "version": "2.7.2", + "from": "esprima@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz" + }, + "esrecurse": { + "version": "4.1.0", + "from": "esrecurse@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz", + "dependencies": { + "estraverse": { + "version": "4.1.1", + "from": "estraverse@>=4.1.0 <4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz" + } + } + }, + "estraverse": { + "version": "4.2.0", + "from": "estraverse@>=4.2.0 <5.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz" + }, + "esutils": { + "version": "2.0.2", + "from": "esutils@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" + }, + "event-emitter": { + "version": "0.3.4", + "from": "event-emitter@>=0.3.4 <0.4.0", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz" + }, + "exec-sh": { + "version": "0.2.0", + "from": "exec-sh@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz" + }, + "exit-hook": { + "version": "1.1.1", + "from": "exit-hook@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz" + }, + "expand-brackets": { + "version": "0.1.5", + "from": "expand-brackets@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" + }, + "expand-range": { + "version": "1.8.2", + "from": "expand-range@>=1.8.1 <2.0.0", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz" + }, + "extend": { + "version": "3.0.0", + "from": "extend@>=3.0.0 <3.1.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + }, + "extglob": { + "version": "0.3.2", + "from": "extglob@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" + }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + }, + "fancy-log": { + "version": "1.2.0", + "from": "fancy-log@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.2.0.tgz" + }, + "fast-levenshtein": { + "version": "1.1.3", + "from": "fast-levenshtein@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.3.tgz" + }, + "fb-watchman": { + "version": "1.9.0", + "from": "fb-watchman@>=1.9.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz" + }, + "fbjs-scripts": { + "version": "0.7.1", + "from": "fbjs-scripts@>=0.7.1 <0.8.0", + "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.7.1.tgz", + "dependencies": { + "core-js": { + "version": "1.2.6", + "from": "core-js@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" + } + } + }, + "figures": { + "version": "1.7.0", + "from": "figures@>=1.3.5 <2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz" + }, + "file-entry-cache": { + "version": "1.2.4", + "from": "file-entry-cache@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.2.4.tgz" + }, + "filename-regex": { + "version": "2.0.0", + "from": "filename-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" + }, + "fileset": { + "version": "0.2.1", + "from": "fileset@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", + "dependencies": { + "glob": { + "version": "5.0.15", + "from": "glob@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + }, + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" + } + } + }, + "fill-range": { + "version": "2.2.3", + "from": "fill-range@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" + }, + "find-up": { + "version": "1.1.2", + "from": "find-up@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "dependencies": { + "path-exists": { + "version": "2.1.0", + "from": "path-exists@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" + } + } + }, + "flat-cache": { + "version": "1.0.10", + "from": "flat-cache@>=1.0.9 <2.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.0.10.tgz" + }, + "flow-parser": { + "version": "0.26.0", + "from": "flow-parser@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.26.0.tgz", + "dependencies": { + "ast-types": { + "version": "0.8.16", + "from": "ast-types@0.8.16", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.16.tgz" + } + } + }, + "for-in": { + "version": "0.1.5", + "from": "for-in@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.5.tgz" + }, + "for-own": { + "version": "0.1.4", + "from": "for-own@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.4.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "1.0.0-rc4", + "from": "form-data@>=1.0.0-rc3 <1.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" + }, + "fs-readdir-recursive": { + "version": "0.1.2", + "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" + }, + "fs.realpath": { + "version": "1.0.0", + "from": "fs.realpath@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" + }, + "get-stdin": { + "version": "4.0.1", + "from": "get-stdin@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + }, + "getpass": { + "version": "0.1.6", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "glob": { + "version": "7.0.5", + "from": "glob@>=7.0.3 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz" + }, + "glob-base": { + "version": "0.3.0", + "from": "glob-base@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" + }, + "glob-parent": { + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" + }, + "globals": { + "version": "8.18.0", + "from": "globals@>=8.3.0 <9.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz" + }, + "globby": { + "version": "5.0.0", + "from": "globby@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz" + }, + "glogg": { + "version": "1.0.0", + "from": "glogg@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz" + }, + "graceful-fs": { + "version": "4.1.4", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz" + }, + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>=1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + }, + "growly": { + "version": "1.3.0", + "from": "growly@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz" + }, + "gulp-util": { + "version": "3.0.7", + "from": "gulp-util@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.7.tgz", + "dependencies": { + "object-assign": { + "version": "3.0.0", + "from": "object-assign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz" + } + } + }, + "gulplog": { + "version": "1.0.0", + "from": "gulplog@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz" + }, + "handlebars": { + "version": "4.0.5", + "from": "handlebars@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.5.tgz", + "dependencies": { + "source-map": { + "version": "0.4.4", + "from": "source-map@>=0.4.4 <0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" + } + } + }, + "har-validator": { + "version": "2.0.6", + "from": "har-validator@>=2.0.6 <2.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-color": { + "version": "0.1.7", + "from": "has-color@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + }, + "has-flag": { + "version": "1.0.0", + "from": "has-flag@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" + }, + "has-gulplog": { + "version": "0.1.0", + "from": "has-gulplog@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@>=3.1.3 <3.2.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "home-or-tmp": { + "version": "1.0.0", + "from": "home-or-tmp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz" + }, + "hosted-git-info": { + "version": "2.1.5", + "from": "hosted-git-info@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.5.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@>=0.4.13 <0.5.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" + }, + "ignore": { + "version": "3.1.3", + "from": "ignore@>=3.1.2 <4.0.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.1.3.tgz" + }, + "imurmurhash": { + "version": "0.1.4", + "from": "imurmurhash@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + }, + "indent-string": { + "version": "2.1.0", + "from": "indent-string@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "dependencies": { + "repeating": { + "version": "2.0.1", + "from": "repeating@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" + } + } + }, + "inflight": { + "version": "1.0.5", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "inquirer": { + "version": "0.12.0", + "from": "inquirer@>=0.12.0 <0.13.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz" + }, + "invariant": { + "version": "2.2.1", + "from": "invariant@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.1.tgz" + }, + "invert-kv": { + "version": "1.0.0", + "from": "invert-kv@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz" + }, + "is-arrayish": { + "version": "0.2.1", + "from": "is-arrayish@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + }, + "is-buffer": { + "version": "1.1.3", + "from": "is-buffer@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz" + }, + "is-builtin-module": { + "version": "1.0.0", + "from": "is-builtin-module@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz" + }, + "is-dotfile": { + "version": "1.0.2", + "from": "is-dotfile@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz" + }, + "is-equal-shallow": { + "version": "0.1.3", + "from": "is-equal-shallow@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" + }, + "is-extendable": { + "version": "0.1.1", + "from": "is-extendable@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + }, + "is-extglob": { + "version": "1.0.0", + "from": "is-extglob@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" + }, + "is-finite": { + "version": "1.0.1", + "from": "is-finite@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" + }, + "is-glob": { + "version": "2.0.1", + "from": "is-glob@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + }, + "is-integer": { + "version": "1.0.6", + "from": "is-integer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" + }, + "is-my-json-valid": { + "version": "2.13.1", + "from": "is-my-json-valid@>=2.10.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" + }, + "is-number": { + "version": "2.1.0", + "from": "is-number@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" + }, + "is-path-cwd": { + "version": "1.0.0", + "from": "is-path-cwd@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz" + }, + "is-path-in-cwd": { + "version": "1.0.0", + "from": "is-path-in-cwd@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz" + }, + "is-path-inside": { + "version": "1.0.0", + "from": "is-path-inside@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz" + }, + "is-posix-bracket": { + "version": "0.1.1", + "from": "is-posix-bracket@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" + }, + "is-primitive": { + "version": "2.0.0", + "from": "is-primitive@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" + }, + "is-property": { + "version": "1.0.2", + "from": "is-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + }, + "is-resolvable": { + "version": "1.0.0", + "from": "is-resolvable@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "is-utf8": { + "version": "0.2.1", + "from": "is-utf8@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isexe": { + "version": "1.1.2", + "from": "isexe@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" + }, + "isobject": { + "version": "2.1.0", + "from": "isobject@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "istanbul": { + "version": "0.4.4", + "from": "istanbul@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.4.tgz", + "dependencies": { + "supports-color": { + "version": "3.1.2", + "from": "supports-color@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" + } + } + }, + "jasmine-check": { + "version": "0.1.5", + "from": "jasmine-check@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/jasmine-check/-/jasmine-check-0.1.5.tgz" + }, + "jest-changed-files": { + "version": "13.0.0", + "from": "jest-changed-files@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-13.0.0.tgz" + }, + "jest-cli": { + "version": "13.0.0", + "from": "jest-cli@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-13.0.0.tgz", + "dependencies": { + "lodash.escape": { + "version": "4.0.0", + "from": "lodash.escape@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.0.tgz" + }, + "lodash.template": { + "version": "4.2.5", + "from": "lodash.template@>=4.2.4 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.2.5.tgz" + }, + "lodash.templatesettings": { + "version": "4.0.1", + "from": "lodash.templatesettings@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.0.1.tgz" + } + } + }, + "jest-config": { + "version": "13.0.0", + "from": "jest-config@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-13.0.0.tgz" + }, + "jest-environment-jsdom": { + "version": "13.0.0", + "from": "jest-environment-jsdom@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-13.0.0.tgz" + }, + "jest-environment-node": { + "version": "13.0.0", + "from": "jest-environment-node@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-13.0.0.tgz" + }, + "jest-haste-map": { + "version": "13.0.0", + "from": "jest-haste-map@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-13.0.0.tgz" + }, + "jest-jasmine1": { + "version": "13.0.0", + "from": "jest-jasmine1@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-jasmine1/-/jest-jasmine1-13.0.0.tgz" + }, + "jest-jasmine2": { + "version": "13.0.0", + "from": "jest-jasmine2@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-13.0.0.tgz" + }, + "jest-mock": { + "version": "13.0.0", + "from": "jest-mock@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-13.0.0.tgz" + }, + "jest-resolve": { + "version": "13.0.0", + "from": "jest-resolve@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-13.0.0.tgz" + }, + "jest-runtime": { + "version": "13.0.0", + "from": "jest-runtime@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-13.0.0.tgz" + }, + "jest-snapshot": { + "version": "13.0.0", + "from": "jest-snapshot@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-13.0.0.tgz" + }, + "jest-util": { + "version": "13.0.0", + "from": "jest-util@>=13.0.0 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-13.0.0.tgz" + }, + "jodid25519": { + "version": "1.0.2", + "from": "jodid25519@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" + }, + "js-tokens": { + "version": "2.0.0", + "from": "js-tokens@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz" + }, + "js-yaml": { + "version": "3.6.1", + "from": "js-yaml@>=3.5.1 <4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz" + }, + "jsbn": { + "version": "0.1.0", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" + }, "jscodeshift": { "version": "0.3.25", + "from": "jscodeshift@0.3.25", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.3.25.tgz", "dependencies": { + "ast-types": { + "version": "0.8.17", + "from": "git+https://github.com/keyanzhang/ast-types.git", + "resolved": "git+https://github.com/keyanzhang/ast-types.git#e2d344814be03c5f9934379d17af62a872e70570" + }, + "babel-core": { + "version": "5.8.38", + "from": "babel-core@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.38.tgz", + "dependencies": { + "babylon": { + "version": "5.8.38", + "from": "babylon@>=5.8.38 <6.0.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz" + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.10.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + } + } + }, + "bluebird": { + "version": "2.10.2", + "from": "bluebird@>=2.9.33 <3.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" + }, + "colors": { + "version": "1.1.2", + "from": "colors@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" + }, + "core-js": { + "version": "1.2.6", + "from": "core-js@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" + }, + "globals": { + "version": "6.4.1", + "from": "globals@>=6.4.0 <7.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz" + }, + "js-tokens": { + "version": "1.0.1", + "from": "js-tokens@1.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz" + }, + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.3 <3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" + }, "recast": { - "version": "https://github.com/keyanzhang/recast" + "version": "0.11.10", + "from": "recast@https://github.com/keyanzhang/recast", + "resolved": "git+https://github.com/keyanzhang/recast.git#c9a839fd620307384ab06e019777ca76c057f522" + } + } + }, + "jsdom": { + "version": "9.3.0", + "from": "jsdom@>=9.2.1 <10.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.3.0.tgz", + "dependencies": { + "acorn": { + "version": "2.7.0", + "from": "acorn@>=2.4.0 <3.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz" + } + } + }, + "jsesc": { + "version": "0.5.0", + "from": "jsesc@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + }, + "json-schema": { + "version": "0.2.2", + "from": "json-schema@0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" + }, + "json-stable-stringify": { + "version": "1.0.1", + "from": "json-stable-stringify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "json5": { + "version": "0.4.0", + "from": "json5@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz" + }, + "jsonify": { + "version": "0.0.0", + "from": "jsonify@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "jsprim": { + "version": "1.3.0", + "from": "jsprim@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz" + }, + "jsx-ast-utils": { + "version": "1.2.1", + "from": "jsx-ast-utils@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.2.1.tgz" + }, + "kind-of": { + "version": "3.0.3", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.3.tgz" + }, + "lazy-cache": { + "version": "1.0.4", + "from": "lazy-cache@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" + }, + "lcid": { + "version": "1.0.0", + "from": "lcid@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" + }, + "leven": { + "version": "1.0.2", + "from": "leven@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz" + }, + "levn": { + "version": "0.3.0", + "from": "levn@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" + }, + "load-json-file": { + "version": "1.1.0", + "from": "load-json-file@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" + }, + "lodash": { + "version": "4.13.1", + "from": "lodash@>=4.2.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz" + }, + "lodash._arraycopy": { + "version": "3.0.0", + "from": "lodash._arraycopy@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz" + }, + "lodash._arrayeach": { + "version": "3.0.0", + "from": "lodash._arrayeach@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz" + }, + "lodash._baseassign": { + "version": "3.2.0", + "from": "lodash._baseassign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "dependencies": { + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + } + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "from": "lodash._baseclone@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "dependencies": { + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + } + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "from": "lodash._basecopy@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz" + }, + "lodash._basefor": { + "version": "3.0.3", + "from": "lodash._basefor@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz" + }, + "lodash._baseiteratee": { + "version": "4.7.0", + "from": "lodash._baseiteratee@>=4.7.0 <4.8.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz" + }, + "lodash._basetostring": { + "version": "4.12.0", + "from": "lodash._basetostring@>=4.12.0 <4.13.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz" + }, + "lodash._basevalues": { + "version": "3.0.0", + "from": "lodash._basevalues@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "from": "lodash._bindcallback@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz" + }, + "lodash._createassigner": { + "version": "3.1.1", + "from": "lodash._createassigner@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz" + }, + "lodash._getnative": { + "version": "3.9.1", + "from": "lodash._getnative@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "from": "lodash._isiterateecall@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz" + }, + "lodash._reescape": { + "version": "3.0.0", + "from": "lodash._reescape@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz" + }, + "lodash._reevaluate": { + "version": "3.0.0", + "from": "lodash._reevaluate@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "from": "lodash._reinterpolate@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" + }, + "lodash._root": { + "version": "3.0.1", + "from": "lodash._root@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz" + }, + "lodash._stringtopath": { + "version": "4.8.0", + "from": "lodash._stringtopath@>=4.8.0 <4.9.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz" + }, + "lodash.assign": { + "version": "4.0.9", + "from": "lodash.assign@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.0.9.tgz" + }, + "lodash.assigninwith": { + "version": "4.0.7", + "from": "lodash.assigninwith@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.assigninwith/-/lodash.assigninwith-4.0.7.tgz" + }, + "lodash.clonedeep": { + "version": "3.0.2", + "from": "lodash.clonedeep@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz" + }, + "lodash.escape": { + "version": "3.2.0", + "from": "lodash.escape@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz" + }, + "lodash.isarguments": { + "version": "3.0.8", + "from": "lodash.isarguments@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.8.tgz" + }, + "lodash.isarray": { + "version": "3.0.4", + "from": "lodash.isarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" + }, + "lodash.keys": { + "version": "4.0.7", + "from": "lodash.keys@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.0.7.tgz" + }, + "lodash.keysin": { + "version": "4.1.4", + "from": "lodash.keysin@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-4.1.4.tgz" + }, + "lodash.pickby": { + "version": "4.4.0", + "from": "lodash.pickby@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.4.0.tgz" + }, + "lodash.rest": { + "version": "4.0.3", + "from": "lodash.rest@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.3.tgz" + }, + "lodash.restparam": { + "version": "3.6.1", + "from": "lodash.restparam@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz" + }, + "lodash.template": { + "version": "3.6.2", + "from": "lodash.template@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "dependencies": { + "lodash._basetostring": { + "version": "3.0.1", + "from": "lodash._basetostring@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" + }, + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + } + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "from": "lodash.templatesettings@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz" + }, + "lodash.tostring": { + "version": "4.1.3", + "from": "lodash.tostring@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.3.tgz" + }, + "longest": { + "version": "1.0.1", + "from": "longest@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" + }, + "loose-envify": { + "version": "1.2.0", + "from": "loose-envify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.2.0.tgz", + "dependencies": { + "js-tokens": { + "version": "1.0.3", + "from": "js-tokens@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.3.tgz" + } + } + }, + "loud-rejection": { + "version": "1.5.0", + "from": "loud-rejection@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.5.0.tgz" + }, + "lru-cache": { + "version": "4.0.1", + "from": "lru-cache@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz" + }, + "makeerror": { + "version": "1.0.11", + "from": "makeerror@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz" + }, + "map-obj": { + "version": "1.0.1", + "from": "map-obj@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + }, + "marked": { + "version": "0.3.5", + "from": "marked@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.5.tgz" + }, + "marked-terminal": { + "version": "1.6.1", + "from": "marked-terminal@>=1.6.1 <2.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.6.1.tgz", + "dependencies": { + "lodash.assign": { + "version": "3.2.0", + "from": "lodash.assign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz" + }, + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + } + } + }, + "meow": { + "version": "3.7.0", + "from": "meow@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" + }, + "merge": { + "version": "1.2.0", + "from": "merge@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz" + }, + "micromatch": { + "version": "2.3.10", + "from": "micromatch@>=2.3.7 <3.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.10.tgz" + }, + "mime-db": { + "version": "1.23.0", + "from": "mime-db@>=1.23.0 <1.24.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" + }, + "mime-types": { + "version": "2.1.11", + "from": "mime-types@>=2.1.7 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" + }, + "minimatch": { + "version": "3.0.2", + "from": "minimatch@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "multipipe": { + "version": "0.1.2", + "from": "multipipe@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz" + }, + "mute-stream": { + "version": "0.0.5", + "from": "mute-stream@0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz" + }, + "node-dir": { + "version": "0.1.8", + "from": "node-dir@0.1.8", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz" + }, + "node-emoji": { + "version": "0.1.0", + "from": "node-emoji@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-0.1.0.tgz" + }, + "node-int64": { + "version": "0.4.0", + "from": "node-int64@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + }, + "node-notifier": { + "version": "4.6.0", + "from": "node-notifier@>=4.6.0 <5.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.6.0.tgz" + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@>=1.4.7 <1.5.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "nomnom": { + "version": "1.8.1", + "from": "nomnom@>=1.8.1 <2.0.0", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "from": "ansi-styles@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz" + }, + "chalk": { + "version": "0.4.0", + "from": "chalk@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz" + }, + "strip-ansi": { + "version": "0.1.1", + "from": "strip-ansi@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz" + } + } + }, + "nopt": { + "version": "3.0.6", + "from": "nopt@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" + }, + "normalize-package-data": { + "version": "2.3.5", + "from": "normalize-package-data@>=2.3.4 <3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz" + }, + "normalize-path": { + "version": "2.0.1", + "from": "normalize-path@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" + }, + "number-is-nan": { + "version": "1.0.0", + "from": "number-is-nan@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" + }, + "nwmatcher": { + "version": "1.3.8", + "from": "nwmatcher@>=1.3.7 <2.0.0", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.8.tgz" + }, + "oauth-sign": { + "version": "0.8.2", + "from": "oauth-sign@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" + }, + "object-assign": { + "version": "4.1.0", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" + }, + "object.omit": { + "version": "2.0.0", + "from": "object.omit@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" + }, + "once": { + "version": "1.3.3", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" + }, + "onetime": { + "version": "1.1.0", + "from": "onetime@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz" + }, + "optimist": { + "version": "0.6.1", + "from": "optimist@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.10", + "from": "minimist@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + }, + "wordwrap": { + "version": "0.0.3", + "from": "wordwrap@>=0.0.2 <0.1.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" + } + } + }, + "optionator": { + "version": "0.8.1", + "from": "optionator@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.1.tgz" + }, + "os-homedir": { + "version": "1.0.1", + "from": "os-homedir@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz" + }, + "os-locale": { + "version": "1.4.0", + "from": "os-locale@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" + }, + "os-tmpdir": { + "version": "1.0.1", + "from": "os-tmpdir@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" + }, + "output-file-sync": { + "version": "1.1.2", + "from": "output-file-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz" + }, + "parse-glob": { + "version": "3.0.4", + "from": "parse-glob@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" + }, + "parse-json": { + "version": "2.2.0", + "from": "parse-json@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" + }, + "parse5": { + "version": "1.5.1", + "from": "parse5@>=1.5.1 <2.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz" + }, + "path-exists": { + "version": "1.0.0", + "from": "path-exists@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz" + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "path-is-inside": { + "version": "1.0.1", + "from": "path-is-inside@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz" + }, + "path-type": { + "version": "1.1.0", + "from": "path-type@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" + }, + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + }, + "pinkie": { + "version": "2.0.4", + "from": "pinkie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + }, + "pinkie-promise": { + "version": "2.0.1", + "from": "pinkie-promise@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + }, + "pkg-conf": { + "version": "1.1.3", + "from": "pkg-conf@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz" + }, + "pluralize": { + "version": "1.2.1", + "from": "pluralize@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz" + }, + "prelude-ls": { + "version": "1.1.2", + "from": "prelude-ls@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + }, + "preserve": { + "version": "0.2.0", + "from": "preserve@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" + }, + "pretty-format": { + "version": "3.3.2", + "from": "pretty-format@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.3.2.tgz" + }, + "private": { + "version": "0.1.6", + "from": "private@>=0.1.6 <0.2.0", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz" + }, + "process-nextick-args": { + "version": "1.0.7", + "from": "process-nextick-args@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" + }, + "progress": { + "version": "1.1.8", + "from": "progress@>=1.1.8 <2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz" + }, + "prr": { + "version": "0.0.0", + "from": "prr@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz" + }, + "pseudomap": { + "version": "1.0.2", + "from": "pseudomap@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" + }, + "q": { + "version": "1.4.1", + "from": "q@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz" + }, + "qs": { + "version": "6.1.0", + "from": "qs@>=6.1.0 <6.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" + }, + "randomatic": { + "version": "1.1.5", + "from": "randomatic@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" + }, + "read-json-sync": { + "version": "1.1.1", + "from": "read-json-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/read-json-sync/-/read-json-sync-1.1.1.tgz" + }, + "read-pkg": { + "version": "1.1.0", + "from": "read-pkg@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" + }, + "read-pkg-up": { + "version": "1.0.1", + "from": "read-pkg-up@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + }, + "readline2": { + "version": "1.0.1", + "from": "readline2@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz" + }, + "recast": { + "version": "0.10.33", + "from": "recast@0.10.33", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + } + } + }, + "redent": { + "version": "1.0.0", + "from": "redent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz" + }, + "redeyed": { + "version": "0.5.0", + "from": "redeyed@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.5.0.tgz", + "dependencies": { + "esprima-fb": { + "version": "12001.1.0-dev-harmony-fb", + "from": "esprima-fb@>=12001.1.0-dev-harmony-fb <12001.2.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-12001.1.0-dev-harmony-fb.tgz" + } + } + }, + "regenerate": { + "version": "1.3.1", + "from": "regenerate@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.1.tgz" + }, + "regenerator": { + "version": "0.8.40", + "from": "regenerator@0.8.40", + "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + } + } + }, + "regenerator-runtime": { + "version": "0.9.5", + "from": "regenerator-runtime@>=0.9.5 <0.10.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz" + }, + "regex-cache": { + "version": "0.4.3", + "from": "regex-cache@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" + }, + "regexpu": { + "version": "1.3.0", + "from": "regexpu@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz" + }, + "regexpu-core": { + "version": "2.0.0", + "from": "regexpu-core@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz" + }, + "regjsgen": { + "version": "0.2.0", + "from": "regjsgen@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz" + }, + "regjsparser": { + "version": "0.1.5", + "from": "regjsparser@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz" + }, + "repeat-element": { + "version": "1.1.2", + "from": "repeat-element@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" + }, + "repeat-string": { + "version": "1.5.4", + "from": "repeat-string@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz" + }, + "repeating": { + "version": "1.1.3", + "from": "repeating@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz" + }, + "replace-ext": { + "version": "0.0.1", + "from": "replace-ext@0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz" + }, + "request": { + "version": "2.72.0", + "from": "request@>=2.55.0 <3.0.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz" + }, + "require-main-filename": { + "version": "1.0.1", + "from": "require-main-filename@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz" + }, + "require-uncached": { + "version": "1.0.2", + "from": "require-uncached@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.2.tgz" + }, + "resolve": { + "version": "1.1.7", + "from": "resolve@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" + }, + "resolve-from": { + "version": "1.0.1", + "from": "resolve-from@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz" + }, + "restore-cursor": { + "version": "1.0.1", + "from": "restore-cursor@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz" + }, + "right-align": { + "version": "0.1.3", + "from": "right-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz" + }, + "rimraf": { + "version": "2.5.2", + "from": "rimraf@>=2.2.8 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz" + }, + "run-async": { + "version": "0.1.0", + "from": "run-async@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz" + }, + "rx-lite": { + "version": "3.1.2", + "from": "rx-lite@>=3.1.2 <4.0.0", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz" + }, + "sane": { + "version": "1.4.0", + "from": "sane@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-1.4.0.tgz" + }, + "sax": { + "version": "1.2.1", + "from": "sax@>=1.1.4 <2.0.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" + }, + "semver": { + "version": "5.2.0", + "from": "semver@>=5.1.0 <6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.2.0.tgz" + }, + "set-blocking": { + "version": "1.0.0", + "from": "set-blocking@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-1.0.0.tgz" + }, + "shebang-regex": { + "version": "1.0.0", + "from": "shebang-regex@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + }, + "shelljs": { + "version": "0.6.0", + "from": "shelljs@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.0.tgz" + }, + "shellwords": { + "version": "0.1.0", + "from": "shellwords@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.0.tgz" + }, + "signal-exit": { + "version": "3.0.0", + "from": "signal-exit@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz" + }, + "simple-fmt": { + "version": "0.1.0", + "from": "simple-fmt@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz" + }, + "simple-is": { + "version": "0.2.0", + "from": "simple-is@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz" + }, + "slash": { + "version": "1.0.0", + "from": "slash@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz" + }, + "slice-ansi": { + "version": "0.0.4", + "from": "slice-ansi@0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "source-map": { + "version": "0.5.6", + "from": "source-map@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + }, + "source-map-support": { + "version": "0.2.10", + "from": "source-map-support@>=0.2.10 <0.3.0", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz", + "dependencies": { + "source-map": { + "version": "0.1.32", + "from": "source-map@0.1.32", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz" + } + } + }, + "sparkles": { + "version": "1.0.0", + "from": "sparkles@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz" + }, + "spdx-correct": { + "version": "1.0.2", + "from": "spdx-correct@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz" + }, + "spdx-exceptions": { + "version": "1.0.4", + "from": "spdx-exceptions@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz" + }, + "spdx-expression-parse": { + "version": "1.0.2", + "from": "spdx-expression-parse@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz" + }, + "spdx-license-ids": { + "version": "1.2.1", + "from": "spdx-license-ids@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz" + }, + "sprintf-js": { + "version": "1.0.3", + "from": "sprintf-js@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + }, + "sshpk": { + "version": "1.8.3", + "from": "sshpk@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "stable": { + "version": "0.1.5", + "from": "stable@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "string-width": { + "version": "1.0.1", + "from": "string-width@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz" + }, + "stringmap": { + "version": "0.2.2", + "from": "stringmap@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz" + }, + "stringset": { + "version": "0.2.1", + "from": "stringset@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz" + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "strip-bom": { + "version": "2.0.0", + "from": "strip-bom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" + }, + "strip-indent": { + "version": "1.0.1", + "from": "strip-indent@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" + }, + "strip-json-comments": { + "version": "1.0.4", + "from": "strip-json-comments@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, + "symbol": { + "version": "0.2.3", + "from": "symbol@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/symbol/-/symbol-0.2.3.tgz" + }, + "symbol-tree": { + "version": "3.1.4", + "from": "symbol-tree@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.4.tgz" + }, + "table": { + "version": "3.7.8", + "from": "table@>=3.7.8 <4.0.0", + "resolved": "https://registry.npmjs.org/table/-/table-3.7.8.tgz" + }, + "temp": { + "version": "0.8.3", + "from": "temp@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "dependencies": { + "rimraf": { + "version": "2.2.8", + "from": "rimraf@>=2.2.6 <2.3.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + } + } + }, + "testcheck": { + "version": "0.1.4", + "from": "testcheck@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/testcheck/-/testcheck-0.1.4.tgz" + }, + "text-table": { + "version": "0.2.0", + "from": "text-table@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + }, + "through": { + "version": "2.3.8", + "from": "through@>=2.3.6 <3.0.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "through2": { + "version": "2.0.1", + "from": "through2@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz" + }, + "time-stamp": { + "version": "1.0.1", + "from": "time-stamp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.0.1.tgz" + }, + "tmpl": { + "version": "1.0.4", + "from": "tmpl@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz" + }, + "to-fast-properties": { + "version": "1.0.2", + "from": "to-fast-properties@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz" + }, + "tough-cookie": { + "version": "2.2.2", + "from": "tough-cookie@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" + }, + "tr46": { + "version": "0.0.3", + "from": "tr46@>=0.0.3 <0.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + }, + "trim-newlines": { + "version": "1.0.0", + "from": "trim-newlines@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" + }, + "trim-right": { + "version": "1.0.1", + "from": "trim-right@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + }, + "try-resolve": { + "version": "1.0.1", + "from": "try-resolve@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" + }, + "tryit": { + "version": "1.0.2", + "from": "tryit@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.2.tgz" + }, + "tryor": { + "version": "0.1.2", + "from": "tryor@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz" + }, + "tunnel-agent": { + "version": "0.4.3", + "from": "tunnel-agent@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" + }, + "tv4": { + "version": "1.2.7", + "from": "tv4@>=1.2.7 <2.0.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.7.tgz" + }, + "tweetnacl": { + "version": "0.13.3", + "from": "tweetnacl@>=0.13.0 <0.14.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" + }, + "type-check": { + "version": "0.3.2", + "from": "type-check@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.5 <0.1.0", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "uglify-js": { + "version": "2.6.4", + "from": "uglify-js@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.6 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + }, + "camelcase": { + "version": "1.2.1", + "from": "camelcase@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" + }, + "yargs": { + "version": "3.10.0", + "from": "yargs@>=3.10.0 <3.11.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "from": "uglify-to-browserify@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz" + }, + "underscore": { + "version": "1.6.0", + "from": "underscore@>=1.6.0 <1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + }, + "user-home": { + "version": "1.1.1", + "from": "user-home@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "from": "validate-npm-package-license@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz" + }, + "verror": { + "version": "1.3.6", + "from": "verror@1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" + }, + "vinyl": { + "version": "0.5.3", + "from": "vinyl@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz" + }, + "walker": { + "version": "1.0.7", + "from": "walker@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz" + }, + "watch": { + "version": "0.10.0", + "from": "watch@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz" + }, + "webidl-conversions": { + "version": "3.0.1", + "from": "webidl-conversions@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + }, + "whatwg-url": { + "version": "3.0.0", + "from": "whatwg-url@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-3.0.0.tgz" + }, + "which": { + "version": "1.2.10", + "from": "which@>=1.2.9 <2.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.10.tgz" + }, + "window-size": { + "version": "0.1.0", + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + }, + "wordwrap": { + "version": "1.0.0", + "from": "wordwrap@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + }, + "worker-farm": { + "version": "1.3.1", + "from": "worker-farm@>=1.3.1 <2.0.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.3.1.tgz" + }, + "wrap-ansi": { + "version": "2.0.0", + "from": "wrap-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz" + }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + }, + "write": { + "version": "0.2.1", + "from": "write@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz" + }, + "xml-name-validator": { + "version": "2.0.1", + "from": "xml-name-validator@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz" + }, + "xregexp": { + "version": "3.1.1", + "from": "xregexp@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-3.1.1.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "y18n": { + "version": "3.2.1", + "from": "y18n@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz" + }, + "yallist": { + "version": "2.0.0", + "from": "yallist@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz" + }, + "yargs": { + "version": "4.7.1", + "from": "yargs@>=4.7.1 <5.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.7.1.tgz", + "dependencies": { + "camelcase": { + "version": "3.0.0", + "from": "camelcase@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz" + }, + "cliui": { + "version": "3.2.0", + "from": "cliui@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz" + }, + "window-size": { + "version": "0.2.0", + "from": "window-size@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz" } } + }, + "yargs-parser": { + "version": "2.4.0", + "from": "yargs-parser@>=2.4.0 <3.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.0.tgz" } } } From f00fbb0d322a2cc2b7b047b16b2e1e2cca09446b Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 29 Jun 2016 17:23:39 -0700 Subject: [PATCH 47/64] rename this.context to context in constructor --- .../class-initial-state.output.js | 2 +- transforms/class.js | 38 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 1523cf76..1a1a47fd 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -56,7 +56,7 @@ class App2 extends React.Component { constructor(props, context) { super(props, context); const state = { - whatever: this.context.whatever, // needs context + whatever: context.whatever, // needs context }; this.state = state; } diff --git a/transforms/class.js b/transforms/class.js index 0d1e5cb4..4a9b5005 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -411,18 +411,30 @@ module.exports = (file, api, options) => { fn.value ), fn); - const updatePropsAccess = getInitialState => - j(getInitialState) - .find(j.MemberExpression, { - object: { - type: 'ThisExpression', - }, - property: { - type: 'Identifier', - name: 'props', - }, - }) - .forEach(path => j(path).replaceWith(j.identifier('props'))); + const updatePropsAndContextAccess = getInitialState => { + const collection = j(getInitialState); + + collection.find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'props', + }, + }).forEach(path => j(path).replaceWith(j.identifier('props'))); + + collection.find(j.MemberExpression, { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'context', + }, + }).forEach(path => j(path).replaceWith(j.identifier('context'))); + }; + const inlineGetInitialState = getInitialState => { const functionExpressionCollection = j(getInitialState.value); @@ -520,7 +532,7 @@ module.exports = (file, api, options) => { hasContextAccess = true; } - updatePropsAccess(getInitialState); + updatePropsAndContextAccess(getInitialState); const constructorArgs = createConstructorArgs(hasContextAccess); return [ From 667e5922988da8f92f2af0cd366c8ffb4c7804fd Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 29 Jun 2016 19:33:41 -0700 Subject: [PATCH 48/64] fixed how we identify shadowing issues --- transforms/class.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/transforms/class.js b/transforms/class.js index 4a9b5005..7591618a 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -253,9 +253,11 @@ module.exports = (file, api, options) => { }; const isInitialStateConvertible = classPath => { - const result = isGetInitialStateConstructorSafe( - ReactUtils.getReactCreateClassSpec(classPath) - ); + const specPath = ReactUtils.getReactCreateClassSpec(classPath); + if (!specPath) { + return false; + } + const result = isGetInitialStateConstructorSafe(findGetInitialState(specPath)); if (!result) { console.warn( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + From 8b15535c2072b49273ec07d6890aa0711b67ce55 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Wed, 29 Jun 2016 20:39:27 -0700 Subject: [PATCH 49/64] updated README.md --- README.md | 63 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5826015f..dd0398c2 100644 --- a/README.md +++ b/README.md @@ -102,35 +102,40 @@ jscodeshift -t react-codemod/transforms/sort-comp.js ``` ### Explanation of the new ES2015 class transform with property initializers - - * 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 transforms them to `static` properties at the very top. - * Takes `getDefaultProps` and inlines it as `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 - `static defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than - one statement it will transform into a self-invoking function like this: - `static 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. - * If there exists references to `this.props` in `getInitialState` then it creates - a constructor and converts `getInitialState` to an assignment to `this.state`; - Otherwise it lifts `getInitialState` to a property initializer (`state = ...;`). - * Transforms class methods to arrow functions as class property initializers - (i.e., to bind them) 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 (lifecycle methods) and ignores functions that are being - called directly (unless it is both called directly and passed around to - somewhere else). +1. Determine if mixins are convertible. We only transform a `createClass` call to an ES6 class component when: + - There are no mixins on the class, or + - `options['pure-component']` is true, the `mixins` property is an array and it _only_ contains pure render mixin (the specific module name can be specified using `options['mixin-module-name']`, which defaults to `react-addons-pure-render-mixin`) +2. Ignore components that: + - Call deprecated APIs. This is very defensive, if the script finds any identifiers called `isMounted`, `getDOMNode`, `replaceProps`, `replaceState` or `setProps` it will skip the component + - Explicitly call `this.getInitialState()` and/or `this.getDefaultProps()` since an ES6 class component will no longer have these methods + - Use `arguments` in methods since arrow functions don't have `arguments`. Also please notice that `arguments` should be [very carefully used](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments) and it's generally better to switch to spread (`...args`) instead + - Have inconvertible `getInitialState()`. Specifically if you have variable declarations like `var props = ...` and the right hand side is not `this.props` then we can't inline the state initialization in the `constructor` due to variable shadowing issues + - Have non-primitive right hand side values (like `foo: getStuff()`) in the class spec +3. Transform it to an ES6 class component + 1. Replace `var A = React.createClass(spec)` with `class A extends React.Component {spec}`. If a component uses pure render mixin and passes the mixins test (as described above), it will extend `React.PureComponent` instead + - Remove the `require`/`import` statement that imports pure render mixin when it's no longer being referenced + 2. Pull out all statics defined on `statics` plus the few special cased statics like `childContextTypes`, `contextTypes`, `displayName`, `getDefaultProps()`, and `propTypes` and transform them to `static` properties (`static propTypes = {...};`) + - If `getDefaultProps()` is simple (i.e. it only contains a return statement that returns an object) it will be converted to a simple assignment (`static defaultProps = {...};`). Otherwise an IIFE (immediately-invoked function expression) will be created (`static 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 + 3. Transform `getInitialState()` + - If there's no `getInitialState()` or the `getInitialState()` function is simple (i.e., it only contains a return statement that returns an object) then we don't need a constructor; `state` will be lifted to a property initializer (`state = {...};`) + - However, if the object contains references to `this` other than `this.props` and/or `this.context`, we can't be sure about what you'll need from `this`. We need to ensure that our property initializers' evaluation order is safe, so we defer `state`'s initialization by moving it all the way down until all other property initializers have been initialized + - If `getInitialState()` is not simple, we create a `constructor` and convert `getInitialState()` to an assignment to `this.state` + - `constructor` always have `props` as the first parameter + - We only put `context` as the second parameter when (one of) the following things happen in `getInitialState()`: + - It accesses `this.context`, or + - There's a direct method call `this.x()`, or + - `this` is referenced alone + - Rewrite accesses to `this.props` to `props` and accesses to `this.context` to `context` since the values will be passed as `constructor` arguments + - Remove _simple_ variable declarations like `var props = this.props;` and `var context = this.context` + - Rewrite top-level return statements (`return {...};`) to `this.state = {...}` + - Add `return;` after the assignment when the return statement is part of a control flow statement (not a direct child of `getInitialState()`'s body) and not in an inner function declaration + 4. Transform all non-lifecycle methods and fields to class property initializers (like `onClick = () => {};`). All your Flow annotations will be preserved + - It's actually not necessary to transform all methods to arrow functions (i.e., to bind them), but this behavior is the same as `createClass()` and we can make sure that we won't accidentally break stuff +4. Generate Flow annotations from `propTypes` and put it on the class (this only happens when there's `/* @flow */` in your code and `options['flow']` is `true`) + - Flow actually understands `propTypes` in `createClass` calls but not ES6 class components. Here the transformation logic is identical to [how](https://github.com/facebook/flow/blob/master/src/typing/statement.ml#L3526) Flow treats `propTypes` + - Notice that `React.PropTypes` and Flow treat optional values differently + - For example, `foo: React.PropTypes.number` is valid when you pass `{}`, `{foo: null}`, or `{foo: undefined}` as props. The equivalent in Flow is actually `foo?: ?number`; the question mark on the left hand side indicates `{}` is valid + - For `propTypes` fields that can't be recognized by Flow, `any` will be used ### Recast Options From 953386a31545e907ffd552b54e0fd358bc986a2f Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 30 Jun 2016 15:42:48 -0700 Subject: [PATCH 50/64] use FlowFixMe for unrecognizable types --- README.md | 2 +- .../__testfixtures__/class-flow2.output.js | 10 +- .../__testfixtures__/class-flow3.output.js | 8 +- .../__testfixtures__/class-flow6.output.js | 18 +- transforms/class.js | 165 ++++++++++-------- 5 files changed, 115 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index dd0398c2..2cbfdd2a 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ jscodeshift -t react-codemod/transforms/sort-comp.js - Flow actually understands `propTypes` in `createClass` calls but not ES6 class components. Here the transformation logic is identical to [how](https://github.com/facebook/flow/blob/master/src/typing/statement.ml#L3526) Flow treats `propTypes` - Notice that `React.PropTypes` and Flow treat optional values differently - For example, `foo: React.PropTypes.number` is valid when you pass `{}`, `{foo: null}`, or `{foo: undefined}` as props. The equivalent in Flow is actually `foo?: ?number`; the question mark on the left hand side indicates `{}` is valid - - For `propTypes` fields that can't be recognized by Flow, `any` will be used + - For `propTypes` fields that can't be recognized by Flow, `$FlowFixMe` will be used ### Recast Options diff --git a/transforms/__testfixtures__/class-flow2.output.js b/transforms/__testfixtures__/class-flow2.output.js index f4345014..3a6b74d5 100644 --- a/transforms/__testfixtures__/class-flow2.output.js +++ b/transforms/__testfixtures__/class-flow2.output.js @@ -15,11 +15,11 @@ const { func } = React.PropTypes */ class IndexRoute extends React.Component { props: { - path?: any, - component?: any, - components?: any, - getComponent?: any, - getComponents?: any, + path?: $FlowFixMe, + component?: $FlowFixMe, + components?: $FlowFixMe, + getComponent?: $FlowFixMe, + getComponents?: $FlowFixMe, }; static createRouteFromReactElement(element, parentRoute) { diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js index f6c0a62d..9a1ebe97 100644 --- a/transforms/__testfixtures__/class-flow3.output.js +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -20,15 +20,15 @@ var optionalFuncShortHand = PropTypes.func; class Component extends React.Component { props: { - optionalFuncShortHand?: any, - optionalNumber?: any, + optionalFuncShortHand?: $FlowFixMe, + optionalNumber?: $FlowFixMe, optionalObject?: ?Object, - optionalString?: any, + optionalString?: $FlowFixMe, optionalNode?: any, optionalElement?: any, optionalMessage?: ?Message, optionalEnum?: ?('News' | 'Photos' | 1 | true | null), - optionalUnion?: any, + optionalUnion?: $FlowFixMe, optionalArrayOf?: ?Array, optionalObjectOf?: ?{[key: string]: ?number}, optionalObjectWithShape?: ?{color?: ?string}, diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js index 2cdd09d3..c327ebfa 100644 --- a/transforms/__testfixtures__/class-flow6.output.js +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -10,21 +10,21 @@ const justNeedKeys = { class Component extends React.Component { props: { optionalMessage?: ?Message, - optionalMessageOops?: any, - optionalEnum?: any, - optionalEnumOops?: any, + optionalMessageOops?: $FlowFixMe, + optionalEnum?: $FlowFixMe, + optionalEnumOops?: $FlowFixMe, optionalUnion?: ?(string | number | Message), - optionalUnionOops?: any, - optionalUnionOops2?: any, + optionalUnionOops?: $FlowFixMe, + optionalUnionOops2?: $FlowFixMe, optionalArrayOf?: ?Array, optionalObjectOf?: ?{[key: string]: ?number}, optionalObjectWithShape?: ?{ color?: ?string, - fontSize?: any, - name?: any, + fontSize?: $FlowFixMe, + name?: $FlowFixMe, }, - optionalObjectWithShapeOops?: any, - optionalObjectWithShapeOops2?: any, + optionalObjectWithShapeOops?: $FlowFixMe, + optionalObjectWithShapeOops2?: $FlowFixMe, 'is-literal-cool'?: ?boolean, 'well-fine': number, }; diff --git a/transforms/class.js b/transforms/class.js index 7591618a..ed86101f 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -601,6 +601,10 @@ module.exports = (file, api, options) => { // Flow! const flowAnyType = j.anyTypeAnnotation(); + const flowFixMeType = j.genericTypeAnnotation( + j.identifier('$FlowFixMe'), + null + ); const literalToFlowType = node => { switch (typeof node.value) { @@ -612,25 +616,25 @@ module.exports = (file, api, options) => { return j.booleanLiteralTypeAnnotation(node.value, node.raw); case 'object': // we already know it's a NullLiteral here return j.nullLiteralTypeAnnotation(); - default: - return flowAnyType; // meh + default: // this should never happen + return flowFixMeType; } }; const propTypeToFlowMapping = { // prim types - any: flowAnyType, + any: flowAnyType, // this is the only real any array: j.genericTypeAnnotation( j.identifier('Array'), j.typeParameterInstantiation([flowAnyType]) ), bool: j.booleanTypeAnnotation(), - element: flowAnyType, + element: flowAnyType, // flow does the same for `element` type in `propTypes` func: j.genericTypeAnnotation( j.identifier('Function'), null ), - node: flowAnyType, + node: flowAnyType, // flow does the same for `node` type in `propTypes` number: j.numberTypeAnnotation(), object: j.genericTypeAnnotation( j.identifier('Object'), @@ -664,7 +668,7 @@ module.exports = (file, api, options) => { const propTypeToFlowAnnotation = val => { let cursor = val; let isOptional = true; - let typeResult = flowAnyType; + let typeResult = flowFixMeType; if ( // check `.isRequired` first cursor.type === 'MemberExpression' && @@ -675,68 +679,72 @@ module.exports = (file, api, options) => { cursor = cursor.object; } - if (cursor.type === 'CallExpression') { // type class - const calleeName = cursor.callee.type === 'MemberExpression' ? - cursor.callee.property.name : - cursor.callee.name; - - const constructor = propTypeToFlowMapping[calleeName]; - if (!constructor) { - typeResult = flowAnyType; - return [typeResult, isOptional]; - } - - switch (cursor.callee.property.name) { - case 'arrayOf': { - const arg = cursor.arguments[0]; - typeResult = constructor( - propTypeToFlowAnnotation(arg)[0] - ); + switch (cursor.type) { + case 'CallExpression': { // type class + const calleeName = cursor.callee.type === 'MemberExpression' ? + cursor.callee.property.name : + cursor.callee.name; + + const constructor = propTypeToFlowMapping[calleeName]; + if (!constructor) { // unknown type class + // it's not necessary since `typeResult` defaults to `flowFixMeType`, + // but it's more explicit this way + typeResult = flowFixMeType; break; } - case 'instanceOf': { - const arg = cursor.arguments[0]; - if (arg.type !== 'Identifier') { - typeResult = flowAnyType; - break; - } - typeResult = constructor(arg); - break; - } - case 'objectOf': { - const arg = cursor.arguments[0]; - const [valueType, isOptional] = propTypeToFlowAnnotation(arg); - typeResult = constructor(valueType, isOptional); - break; - } - case 'oneOf': { - const argList = cursor.arguments[0].elements; - if (!argList || !argList.every(node => node.type === 'Literal')) { - typeResult = flowAnyType; - } else { + switch (cursor.callee.property.name) { + case 'arrayOf': { + const arg = cursor.arguments[0]; typeResult = constructor( - argList.map(literalToFlowType) + propTypeToFlowAnnotation(arg)[0] ); + break; } - break; - } - case 'oneOfType': { - const argList = cursor.arguments[0].elements; - if (!argList) { - typeResult = flowAnyType; - } else { - typeResult = constructor( - argList.map(arg => propTypeToFlowAnnotation(arg)[0]) - ); + case 'instanceOf': { + const arg = cursor.arguments[0]; + if (arg.type !== 'Identifier') { + typeResult = flowFixMeType; + break; + } + + typeResult = constructor(arg); + break; } - break; - } - case 'shape': { - const rawPropList = cursor.arguments[0].properties; - if (!rawPropList) { - typeResult = flowAnyType; - } else { + case 'objectOf': { + const arg = cursor.arguments[0]; + const [valueType, isOptional] = propTypeToFlowAnnotation(arg); + typeResult = constructor(valueType, isOptional); + break; + } + case 'oneOf': { + const argList = cursor.arguments[0].elements; + if (!argList || !argList.every(node => node.type === 'Literal')) { + typeResult = flowFixMeType; + } else { + typeResult = constructor( + argList.map(literalToFlowType) + ); + } + break; + } + case 'oneOfType': { + const argList = cursor.arguments[0].elements; + if (!argList) { + typeResult = flowFixMeType; + } else { + typeResult = constructor( + argList.map(arg => propTypeToFlowAnnotation(arg)[0]) + ); + } + break; + } + case 'shape': { + const rawPropList = cursor.arguments[0].properties; + if (!rawPropList) { + typeResult = flowFixMeType; + break; + } const flowPropList = []; rawPropList.forEach(typeProp => { const keyIsLiteral = typeProp.key.type === 'Literal'; @@ -745,7 +753,8 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); flowPropList.push(j.objectTypeProperty( keyIsLiteral ? j.literal(name) : j.identifier(name), - isOptional && valueType !== flowAnyType ? + // it doesn't make sense to have `?any` or `?$FlowFixMe` + isOptional && valueType !== flowAnyType && valueType !== flowFixMeType ? j.nullableTypeAnnotation(valueType) : valueType, isOptional @@ -753,15 +762,32 @@ module.exports = (file, api, options) => { }); typeResult = constructor(flowPropList); + break; + } + default: { + break; } + } + break; + } + case 'MemberExpression': { // prim type + if (cursor.property.type !== 'Identifier') { // unrecognizable + typeResult = flowFixMeType; break; } + + const maybeType = propTypeToFlowMapping[cursor.property.name]; + if (maybeType) { + typeResult = propTypeToFlowMapping[cursor.property.name]; + } else { // type not found + typeResult = flowFixMeType; + } + + break; + } + default: { // unrecognizable + break; } - } else if ( // prim type - cursor.type === 'MemberExpression' && - cursor.property.type === 'Identifier' - ) { - typeResult = propTypeToFlowMapping[cursor.property.name] || flowAnyType; } return [typeResult, isOptional]; @@ -775,7 +801,7 @@ module.exports = (file, api, options) => { } prop.value.properties.forEach(typeProp => { - if (!typeProp.key) { // SpreadProperty + if (!typeProp.key) { // stuff like SpreadProperty return; } @@ -785,7 +811,8 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); typePropertyList.push(j.objectTypeProperty( keyIsLiteral ? j.literal(name) : j.identifier(name), - isOptional && valueType !== flowAnyType ? + // it doesn't make sense to have `?any` or `?$FlowFixMe` + isOptional && valueType !== flowAnyType && valueType !== flowFixMeType ? j.nullableTypeAnnotation(valueType) : valueType, isOptional From 3e625302502a5cb44c0f3f2e3f740fd664690c57 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 30 Jun 2016 19:26:03 -0700 Subject: [PATCH 51/64] retain getInitialState()'s return type when possible --- .../class-initial-state.input.js | 47 +++++++++++++++++++ .../class-initial-state.output.js | 41 ++++++++++++++++ transforms/class.js | 2 +- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index 12f300dc..b059ea6c 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -256,3 +256,50 @@ var ShadowingButFine = React.createClass({ return null; }, }); + +// move type annotations +var WithSimpleType = React.createClass({ + getInitialState(): Object { + return { + x: 12, + y: 13, + z: 14, + }; + }, + + render() { + return null; + }, +}); + +var WithLongType = React.createClass({ + getInitialState(): {name: string, age: number, counter: number} { + return { + name: 'Michael', + age: 23, + count: 6, + }; + }, + + render() { + return null; + }, +}); + +var WithMultiLineType = React.createClass({ + getInitialState(): { + nameLists: Array>, + age?: ?number, + counter?: ?number, + } { + return { + nameLists: [['James']], + count: 1400, + foo: 'bar', + }; + }, + + render() { + return null; + }, +}); diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index 1a1a47fd..c6b814e9 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -265,3 +265,44 @@ class ShadowingButFine extends React.Component { return null; } } + +// move type annotations +class WithSimpleType extends React.Component { + state: Object = { + x: 12, + y: 13, + z: 14, + }; + + render() { + return null; + } +} + +class WithLongType extends React.Component { + state: {name: string, age: number, counter: number} = { + name: 'Michael', + age: 23, + count: 6, + }; + + render() { + return null; + } +} + +class WithMultiLineType extends React.Component { + state: { + nameLists: Array>, + age?: ?number, + counter?: ?number, + } = { + nameLists: [['James']], + count: 1400, + foo: 'bar', + }; + + render() { + return null; + } +} diff --git a/transforms/class.js b/transforms/class.js index ed86101f..6ef313d7 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -500,7 +500,7 @@ module.exports = (file, api, options) => { withComments(j.classProperty( j.identifier('state'), pickReturnValueOrCreateIIFE(getInitialState.value), - null, + getInitialState.value.returnType, false ), getInitialState); From 3f700c14e7a492a2b23bccc6b97d0839c9ac0b31 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 30 Jun 2016 19:35:48 -0700 Subject: [PATCH 52/64] stricter checking of referencing APIs that will be removed --- transforms/class.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/transforms/class.js b/transforms/class.js index 6ef313d7..69745dc7 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -172,11 +172,18 @@ module.exports = (file, api, options) => { return true; }; - const hasNoCallsToAPIsThatWillBeRemoved = classPath => { + const hasNoRefsToAPIsThatWillBeRemoved = classPath => { const hasInvalidCalls = ( - j(classPath).find(j.Identifier, {name: DEFAULT_PROPS_FIELD}).size() > 1 || - j(classPath).find(j.Identifier, {name: GET_INITIAL_STATE_FIELD}).size() > 1 + j(classPath).find(j.MemberExpression, { + object: {type: 'ThisExpression'}, + property: {name: DEFAULT_PROPS_FIELD}, + }).size() > 0 || + j(classPath).find(j.MemberExpression, { + object: {type: 'ThisExpression'}, + property: {name: GET_INITIAL_STATE_FIELD}, + }).size() > 0 ); + if (hasInvalidCalls) { console.warn( file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + @@ -1056,7 +1063,7 @@ module.exports = (file, api, options) => { path .filter(mixinsFilter) .filter(hasNoCallsToDeprecatedAPIs) - .filter(hasNoCallsToAPIsThatWillBeRemoved) + .filter(hasNoRefsToAPIsThatWillBeRemoved) .filter(doesNotUseArguments) .filter(isInitialStateConvertible) .filter(canConvertToClass) From 51494acaf8ebe0ec8ac604f39431fe80f51dfc44 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Sat, 2 Jul 2016 00:29:30 -0700 Subject: [PATCH 53/64] optional !== nullable --- README.md | 4 +- .../__testfixtures__/class-flow1.input.js | 6 +-- .../__testfixtures__/class-flow1.output.js | 38 +++++++++---------- .../__testfixtures__/class-flow3.output.js | 12 +++--- .../__testfixtures__/class-flow6.output.js | 14 +++---- transforms/class.js | 23 ++++------- 6 files changed, 45 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 2cbfdd2a..557e835a 100644 --- a/README.md +++ b/README.md @@ -133,8 +133,8 @@ jscodeshift -t react-codemod/transforms/sort-comp.js - It's actually not necessary to transform all methods to arrow functions (i.e., to bind them), but this behavior is the same as `createClass()` and we can make sure that we won't accidentally break stuff 4. Generate Flow annotations from `propTypes` and put it on the class (this only happens when there's `/* @flow */` in your code and `options['flow']` is `true`) - Flow actually understands `propTypes` in `createClass` calls but not ES6 class components. Here the transformation logic is identical to [how](https://github.com/facebook/flow/blob/master/src/typing/statement.ml#L3526) Flow treats `propTypes` - - Notice that `React.PropTypes` and Flow treat optional values differently - - For example, `foo: React.PropTypes.number` is valid when you pass `{}`, `{foo: null}`, or `{foo: undefined}` as props. The equivalent in Flow is actually `foo?: ?number`; the question mark on the left hand side indicates `{}` is valid + - Notice that Flow treats an optional propType as non-nullable + - For example, `foo: React.PropTypes.number` is valid when you pass `{}`, `{foo: null}`, or `{foo: undefined}` as props at **runtime**. However, when Flow infers type from a `createClass` call, only `{}` and `{foo: undefined}` are valid; `{foo: null}` is not. Thus the equivalent type annotation in Flow is actually `{foo?: number}`. The question mark on the left hand side indicates `{}` and `{foo: undefined}` are fine, but when `foo` is present it must be a `number` - For `propTypes` fields that can't be recognized by Flow, `$FlowFixMe` will be used ### Recast Options diff --git a/transforms/__testfixtures__/class-flow1.input.js b/transforms/__testfixtures__/class-flow1.input.js index de673c86..0bad81dd 100644 --- a/transforms/__testfixtures__/class-flow1.input.js +++ b/transforms/__testfixtures__/class-flow1.input.js @@ -21,9 +21,9 @@ var Component = React.createClass({ ]), optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), - objectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, - objectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, + optionalObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), + requiredObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, + requiredObjectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number.isRequired, diff --git a/transforms/__testfixtures__/class-flow1.output.js b/transforms/__testfixtures__/class-flow1.output.js index 658eddb1..3fcae46e 100644 --- a/transforms/__testfixtures__/class-flow1.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -4,24 +4,24 @@ var React = require('react'); class Component extends React.Component { props: { - optionalArray?: ?Array, - optionalBool?: ?boolean, - optionalFunc?: ?Function, - optionalNumber?: ?number, - optionalObject?: ?Object, - optionalString?: ?string, + optionalArray?: Array, + optionalBool?: boolean, + optionalFunc?: Function, + optionalNumber?: number, + optionalObject?: Object, + optionalString?: string, optionalNode?: any, optionalElement?: any, - optionalMessage?: ?Message, - optionalEnum?: ?('News' | 'Photos' | 1 | true | null), - optionalUnion?: ?(string | number | Message), - optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number}, - optionalObjectOfNonOptionalField?: ?{[key: string]: number}, - objectOfNonOptionalField: {[key: string]: number}, - objectOfOptionalField: {[key: string]: ?number}, - optionalObjectWithShape?: ?{ - color?: ?string, + optionalMessage?: Message, + optionalEnum?: 'News' | 'Photos' | 1 | true | null, + optionalUnion?: string | number | Message, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number}, + optionalObjectOfRequiredField?: {[key: string]: number}, + requiredObjectOfRequiredField: {[key: string]: number}, + requiredObjectOfOptionalField: {[key: string]: number}, + optionalObjectWithShape?: { + color?: string, fontSize: number, }, requiredFunc: Function, @@ -46,9 +46,9 @@ class Component extends React.Component { ]), optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), - optionalObjectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), - objectOfNonOptionalField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, - objectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, + optionalObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired), + requiredObjectOfRequiredField: React.PropTypes.objectOf(React.PropTypes.number.isRequired).isRequired, + requiredObjectOfOptionalField: React.PropTypes.objectOf(React.PropTypes.number).isRequired, optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number.isRequired, diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js index 9a1ebe97..18047e63 100644 --- a/transforms/__testfixtures__/class-flow3.output.js +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -22,16 +22,16 @@ class Component extends React.Component { props: { optionalFuncShortHand?: $FlowFixMe, optionalNumber?: $FlowFixMe, - optionalObject?: ?Object, + optionalObject?: Object, optionalString?: $FlowFixMe, optionalNode?: any, optionalElement?: any, - optionalMessage?: ?Message, - optionalEnum?: ?('News' | 'Photos' | 1 | true | null), + optionalMessage?: Message, + optionalEnum?: 'News' | 'Photos' | 1 | true | null, optionalUnion?: $FlowFixMe, - optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number}, - optionalObjectWithShape?: ?{color?: ?string}, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number}, + optionalObjectWithShape?: {color?: string}, requiredFunc: Function, requiredAny: any, }; diff --git a/transforms/__testfixtures__/class-flow6.output.js b/transforms/__testfixtures__/class-flow6.output.js index c327ebfa..1ac3ff07 100644 --- a/transforms/__testfixtures__/class-flow6.output.js +++ b/transforms/__testfixtures__/class-flow6.output.js @@ -9,23 +9,23 @@ const justNeedKeys = { class Component extends React.Component { props: { - optionalMessage?: ?Message, + optionalMessage?: Message, optionalMessageOops?: $FlowFixMe, optionalEnum?: $FlowFixMe, optionalEnumOops?: $FlowFixMe, - optionalUnion?: ?(string | number | Message), + optionalUnion?: string | number | Message, optionalUnionOops?: $FlowFixMe, optionalUnionOops2?: $FlowFixMe, - optionalArrayOf?: ?Array, - optionalObjectOf?: ?{[key: string]: ?number}, - optionalObjectWithShape?: ?{ - color?: ?string, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number}, + optionalObjectWithShape?: { + color?: string, fontSize?: $FlowFixMe, name?: $FlowFixMe, }, optionalObjectWithShapeOops?: $FlowFixMe, optionalObjectWithShapeOops2?: $FlowFixMe, - 'is-literal-cool'?: ?boolean, + 'is-literal-cool'?: boolean, 'well-fine': number, }; diff --git a/transforms/class.js b/transforms/class.js index 69745dc7..59cfef01 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -630,7 +630,7 @@ module.exports = (file, api, options) => { const propTypeToFlowMapping = { // prim types - any: flowAnyType, // this is the only real any + any: flowAnyType, array: j.genericTypeAnnotation( j.identifier('Array'), j.typeParameterInstantiation([flowAnyType]) @@ -658,13 +658,11 @@ module.exports = (file, api, options) => { type, null ), - objectOf: (type, isOptional) => j.objectTypeAnnotation([], [ + objectOf: (type) => j.objectTypeAnnotation([], [ j.objectTypeIndexer( j.identifier('key'), j.stringTypeAnnotation(), - isOptional && type !== flowAnyType ? - j.nullableTypeAnnotation(type) : - type + type ) ]), oneOf: (typeList) => j.unionTypeAnnotation(typeList), @@ -720,8 +718,9 @@ module.exports = (file, api, options) => { } case 'objectOf': { const arg = cursor.arguments[0]; - const [valueType, isOptional] = propTypeToFlowAnnotation(arg); - typeResult = constructor(valueType, isOptional); + typeResult = constructor( + propTypeToFlowAnnotation(arg)[0] + ); break; } case 'oneOf': { @@ -760,10 +759,7 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); flowPropList.push(j.objectTypeProperty( keyIsLiteral ? j.literal(name) : j.identifier(name), - // it doesn't make sense to have `?any` or `?$FlowFixMe` - isOptional && valueType !== flowAnyType && valueType !== flowFixMeType ? - j.nullableTypeAnnotation(valueType) : - valueType, + valueType, isOptional )); }); @@ -818,10 +814,7 @@ module.exports = (file, api, options) => { const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value); typePropertyList.push(j.objectTypeProperty( keyIsLiteral ? j.literal(name) : j.identifier(name), - // it doesn't make sense to have `?any` or `?$FlowFixMe` - isOptional && valueType !== flowAnyType && valueType !== flowFixMeType ? - j.nullableTypeAnnotation(valueType) : - valueType, + valueType, isOptional )); }); From ab1e1a7fb85b4d6632b72874ba1f86d0e58fbee6 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Sat, 2 Jul 2016 20:38:39 -0700 Subject: [PATCH 54/64] support void (undefined) in PropTypes.oneOf() --- transforms/__testfixtures__/class-flow1.input.js | 2 +- transforms/__testfixtures__/class-flow1.output.js | 4 ++-- transforms/class.js | 12 +++++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/transforms/__testfixtures__/class-flow1.input.js b/transforms/__testfixtures__/class-flow1.input.js index 0bad81dd..da855cb9 100644 --- a/transforms/__testfixtures__/class-flow1.input.js +++ b/transforms/__testfixtures__/class-flow1.input.js @@ -13,7 +13,7 @@ var Component = React.createClass({ optionalNode: React.PropTypes.node, optionalElement: React.PropTypes.element, optionalMessage: React.PropTypes.instanceOf(Message), - optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null, undefined]), optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, diff --git a/transforms/__testfixtures__/class-flow1.output.js b/transforms/__testfixtures__/class-flow1.output.js index 3fcae46e..0da57e38 100644 --- a/transforms/__testfixtures__/class-flow1.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -13,7 +13,7 @@ class Component extends React.Component { optionalNode?: any, optionalElement?: any, optionalMessage?: Message, - optionalEnum?: 'News' | 'Photos' | 1 | true | null, + optionalEnum?: 'News' | 'Photos' | 1 | true | null | void, optionalUnion?: string | number | Message, optionalArrayOf?: Array, optionalObjectOf?: {[key: string]: number}, @@ -38,7 +38,7 @@ class Component extends React.Component { optionalNode: React.PropTypes.node, optionalElement: React.PropTypes.element, optionalMessage: React.PropTypes.instanceOf(Message), - optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null]), + optionalEnum: React.PropTypes.oneOf(['News', 'Photos', 1, true, null, undefined]), optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, diff --git a/transforms/class.js b/transforms/class.js index 59cfef01..f0a30bb3 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -614,6 +614,10 @@ module.exports = (file, api, options) => { ); const literalToFlowType = node => { + if (node.type === 'Identifier' && node.name === 'undefined') { + return j.voidTypeAnnotation(); + } + switch (typeof node.value) { case 'string': return j.stringLiteralTypeAnnotation(node.value, node.raw); @@ -725,7 +729,13 @@ module.exports = (file, api, options) => { } case 'oneOf': { const argList = cursor.arguments[0].elements; - if (!argList || !argList.every(node => node.type === 'Literal')) { + if ( + !argList || + !argList.every(node => + (node.type === 'Literal') || + (node.type === 'Identifier' && node.name === 'undefined') + ) + ) { typeResult = flowFixMeType; } else { typeResult = constructor( From 62978f4e634cb7845cfd8a4b0401306592f6556d Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 5 Jul 2016 13:01:37 -0700 Subject: [PATCH 55/64] annotate state type when it's inlined in the constructor --- .../class-initial-state.input.js | 8 ++++++-- .../class-initial-state.output.js | 20 +++++++++++++++++++ transforms/class.js | 20 ++++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/transforms/__testfixtures__/class-initial-state.input.js b/transforms/__testfixtures__/class-initial-state.input.js index b059ea6c..b5ecfdaf 100644 --- a/transforms/__testfixtures__/class-initial-state.input.js +++ b/transforms/__testfixtures__/class-initial-state.input.js @@ -1,8 +1,12 @@ +/* @flow */ + import React from 'React'; +type SomeState = {foo: string}; + // only needs props var MyComponent = React.createClass({ - getInitialState: function() { + getInitialState: function(): {heyoo: number} { var x = this.props.foo; return { heyoo: 23, @@ -37,7 +41,7 @@ var ComponentWithBothPropsAndContextAccess = React.createClass({ }); const App = React.createClass({ - getInitialState() { + getInitialState(): SomeState { const state = this.calculateState(); // _might_ use `this.context` return state; }, diff --git a/transforms/__testfixtures__/class-initial-state.output.js b/transforms/__testfixtures__/class-initial-state.output.js index c6b814e9..e71740c0 100644 --- a/transforms/__testfixtures__/class-initial-state.output.js +++ b/transforms/__testfixtures__/class-initial-state.output.js @@ -1,7 +1,13 @@ +/* @flow */ + import React from 'React'; +type SomeState = {foo: string}; + // only needs props class MyComponent extends React.Component { + state: {heyoo: number}; + constructor(props) { super(props); var x = props.foo; @@ -37,6 +43,8 @@ class ComponentWithBothPropsAndContextAccess extends React.Component { } class App extends React.Component { + state: SomeState; + constructor(props, context) { super(props, context); const state = this.calculateState(); // _might_ use `this.context` @@ -53,6 +61,8 @@ class App extends React.Component { } class App2 extends React.Component { + state: Object; + constructor(props, context) { super(props, context); const state = { @@ -71,6 +81,8 @@ App.contextTypes = { }; class MyComponent2 extends React.Component { + state: Object; + constructor(props) { super(props); var x = props.foo.bar.wow.so.deep; @@ -88,6 +100,8 @@ class MyComponent2 extends React.Component { const getContextFromInstance = (x) => x.context; // meh class MyComponent3 extends React.Component { + state: Object; + constructor(props, context) { super(props, context); var x = getContextFromInstance(this); // `this` is referenced alone @@ -127,6 +141,8 @@ class MyComponent5 extends React.Component { // intense control flow testing class Loader extends React.Component { + state: Object; + constructor(props, context) { super(props, context); if (props.stuff) { @@ -167,6 +183,8 @@ class Loader extends React.Component { } class FunctionDeclarationInGetInitialState extends React.Component { + state: Object; + constructor(props) { super(props); function func() { @@ -256,6 +274,8 @@ var ShadowingIssue = React.createClass({ // bail out here // will remove unnecessary bindings class ShadowingButFine extends React.Component { + state: Object; + constructor(props, context) { super(props, context); this.state = { x: props.x + context.x }; diff --git a/transforms/class.js b/transforms/class.js index f0a30bb3..1eef613e 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -892,8 +892,9 @@ module.exports = (file, api, options) => { rawProperties, comments ) => { - let maybeConstructor = []; const initialStateProperty = []; + let maybeConstructor = []; + let maybeFlowStateAnnotation = []; // we only need this when we do `this.state = ...` if (isInitialStateLiftable(getInitialState)) { if (getInitialState) { @@ -901,6 +902,22 @@ module.exports = (file, api, options) => { } } else { maybeConstructor = createConstructor(getInitialState); + if (shouldTransformFlow) { + let stateType = j.typeAnnotation( + j.genericTypeAnnotation(j.identifier('Object'), null) + ); + + if (getInitialState.value.returnType) { + stateType = getInitialState.value.returnType; + } + + maybeFlowStateAnnotation.push(j.classProperty( + j.identifier('state'), + null, + stateType, + false + )); + } } const propertiesAndMethods = rawProperties.map(prop => { @@ -926,6 +943,7 @@ module.exports = (file, api, options) => { j.classBody( [].concat( flowPropsAnnotation, + maybeFlowStateAnnotation, staticProperties, maybeConstructor, repositionStateProperty(initialStateProperty, propertiesAndMethods) From ee36b617c8ce01997e0839a0736dfa4e42018f1e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 5 Jul 2016 15:00:50 -0700 Subject: [PATCH 56/64] added 'remove-runtime-proptypes' option --- .../__testfixtures__/class-flow7.input.js | 41 +++++++++++++++++++ .../__testfixtures__/class-flow7.output.js | 37 +++++++++++++++++ transforms/__tests__/class-test.js | 9 +++- transforms/class.js | 8 +++- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 transforms/__testfixtures__/class-flow7.input.js create mode 100644 transforms/__testfixtures__/class-flow7.output.js diff --git a/transforms/__testfixtures__/class-flow7.input.js b/transforms/__testfixtures__/class-flow7.input.js new file mode 100644 index 00000000..b008f814 --- /dev/null +++ b/transforms/__testfixtures__/class-flow7.input.js @@ -0,0 +1,41 @@ +/* @flow */ + +var React = require('react'); + +const justNeedKeys = { + a: 12, + b: 23, +}; + +var Component = React.createClass({ + propTypes: { + optionalMessage: React.PropTypes.instanceOf(Message), + optionalMessageOops: React.PropTypes.instanceOf(foo()), + optionalEnum: React.PropTypes.oneOf(Object.keys(justNeedKeys)), + optionalEnumOops: React.PropTypes.oneOf(bar), + optionalUnion: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.number, + React.PropTypes.instanceOf(Message), + ]), + optionalUnionOops: React.PropTypes.oneOfType(foo()), + optionalUnionOops2: React.PropTypes.oneOfType(Bar), + optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), + optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), + optionalObjectWithShape: React.PropTypes.shape({ + color: React.PropTypes.string, + fontSize: foo, + name: bla(), + }), + optionalObjectWithShapeOops: React.PropTypes.shape(foo()), + optionalObjectWithShapeOops2: React.PropTypes.shape(bla), + 'is-literal-cool': React.PropTypes.bool, + 'well-fine': React.PropTypes.number.isRequired, + }, + + render: function() { + return ( +
type safety
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-flow7.output.js b/transforms/__testfixtures__/class-flow7.output.js new file mode 100644 index 00000000..a30533bc --- /dev/null +++ b/transforms/__testfixtures__/class-flow7.output.js @@ -0,0 +1,37 @@ +/* @flow */ + +var React = require('react'); + +const justNeedKeys = { + a: 12, + b: 23, +}; + +class Component extends React.Component { + props: { + optionalMessage?: Message, + optionalMessageOops?: $FlowFixMe, + optionalEnum?: $FlowFixMe, + optionalEnumOops?: $FlowFixMe, + optionalUnion?: string | number | Message, + optionalUnionOops?: $FlowFixMe, + optionalUnionOops2?: $FlowFixMe, + optionalArrayOf?: Array, + optionalObjectOf?: {[key: string]: number}, + optionalObjectWithShape?: { + color?: string, + fontSize?: $FlowFixMe, + name?: $FlowFixMe, + }, + optionalObjectWithShapeOops?: $FlowFixMe, + optionalObjectWithShapeOops2?: $FlowFixMe, + 'is-literal-cool'?: boolean, + 'well-fine': number, + }; + + render() { + return ( +
type safety
+ ); + } +} diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 5d05cbea..4eb3f59b 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -24,7 +24,10 @@ defineTest(__dirname, 'class', enableFlowOption, 'class-anonymous'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); defineTest(__dirname, 'class', enableFlowOption, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); -defineTest(__dirname, 'class', {...enableFlowOption, 'pure-component': true}, 'class-pure-mixin2'); +defineTest(__dirname, 'class', { + ...enableFlowOption, + 'pure-component': true, +}, 'class-pure-mixin2'); defineTest(__dirname, 'class', null, 'class-pure-mixin3'); defineTest(__dirname, 'class', enableFlowOption, 'class-initial-state'); defineTest(__dirname, 'class', enableFlowOption, 'class-property-field'); @@ -34,3 +37,7 @@ defineTest(__dirname, 'class', enableFlowOption, 'class-flow3'); defineTest(__dirname, 'class', enableFlowOption, 'class-flow4'); defineTest(__dirname, 'class', enableFlowOption, 'class-flow5'); defineTest(__dirname, 'class', enableFlowOption, 'class-flow6'); +defineTest(__dirname, 'class', { + ...enableFlowOption, + 'remove-runtime-proptypes': true, +}, 'class-flow7'); diff --git a/transforms/class.js b/transforms/class.js index 1eef613e..81d5a2ac 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -938,13 +938,19 @@ module.exports = (file, api, options) => { ) : []; + let finalStaticProperties = staticProperties; + + if (shouldTransformFlow && options['remove-runtime-proptypes']) { + finalStaticProperties = staticProperties.filter((prop) => prop.key.name !== 'propTypes'); + } + return withComments(j.classDeclaration( name ? j.identifier(name) : null, j.classBody( [].concat( flowPropsAnnotation, maybeFlowStateAnnotation, - staticProperties, + finalStaticProperties, maybeConstructor, repositionStateProperty(initialStateProperty, propertiesAndMethods) ) From 101155286abbc830249d980ca8748cb928aad32e Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 5 Jul 2016 15:06:58 -0700 Subject: [PATCH 57/64] changed inexplicit any to FlowFixMe --- transforms/__testfixtures__/class-flow1.output.js | 6 +++--- transforms/__testfixtures__/class-flow3.output.js | 4 ++-- transforms/class.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/transforms/__testfixtures__/class-flow1.output.js b/transforms/__testfixtures__/class-flow1.output.js index 0da57e38..7e506225 100644 --- a/transforms/__testfixtures__/class-flow1.output.js +++ b/transforms/__testfixtures__/class-flow1.output.js @@ -4,14 +4,14 @@ var React = require('react'); class Component extends React.Component { props: { - optionalArray?: Array, + optionalArray?: Array<$FlowFixMe>, optionalBool?: boolean, optionalFunc?: Function, optionalNumber?: number, optionalObject?: Object, optionalString?: string, - optionalNode?: any, - optionalElement?: any, + optionalNode?: $FlowFixMe, + optionalElement?: $FlowFixMe, optionalMessage?: Message, optionalEnum?: 'News' | 'Photos' | 1 | true | null | void, optionalUnion?: string | number | Message, diff --git a/transforms/__testfixtures__/class-flow3.output.js b/transforms/__testfixtures__/class-flow3.output.js index 18047e63..932b2337 100644 --- a/transforms/__testfixtures__/class-flow3.output.js +++ b/transforms/__testfixtures__/class-flow3.output.js @@ -24,8 +24,8 @@ class Component extends React.Component { optionalNumber?: $FlowFixMe, optionalObject?: Object, optionalString?: $FlowFixMe, - optionalNode?: any, - optionalElement?: any, + optionalNode?: $FlowFixMe, + optionalElement?: $FlowFixMe, optionalMessage?: Message, optionalEnum?: 'News' | 'Photos' | 1 | true | null, optionalUnion?: $FlowFixMe, diff --git a/transforms/class.js b/transforms/class.js index 81d5a2ac..a008d068 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -637,15 +637,15 @@ module.exports = (file, api, options) => { any: flowAnyType, array: j.genericTypeAnnotation( j.identifier('Array'), - j.typeParameterInstantiation([flowAnyType]) + j.typeParameterInstantiation([flowFixMeType]) ), bool: j.booleanTypeAnnotation(), - element: flowAnyType, // flow does the same for `element` type in `propTypes` + element: flowFixMeType, // flow does the same for `element` type in `propTypes` func: j.genericTypeAnnotation( j.identifier('Function'), null ), - node: flowAnyType, // flow does the same for `node` type in `propTypes` + node: flowFixMeType, // flow does the same for `node` type in `propTypes` number: j.numberTypeAnnotation(), object: j.genericTypeAnnotation( j.identifier('Object'), From 847de6d23b0585a74e43fbffcfdf6e94d3c961db Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Tue, 5 Jul 2016 21:51:28 -0700 Subject: [PATCH 58/64] Redesigned the way we start modding components. Now we search and grab `React.createClass` _call expressions_ directly. The only time that we can't simply replace a `createClass` call path with a new class is when the parent of that is a variable declaration: `var Foo = React.createClass({...});` needs to be replaced entirely with `class Foo extends React.Component {...}` Now we delay checking it and only when we need to replace a path we take a look at `path.parentPath.value.type` to see if it's a variable declarator. With this commit we should be able to mod any kind of anonymous `createClass` calls no matter how they are defined. --- .../class-anonymous2.input.js | 50 ++++++++ .../class-anonymous2.output.js | 50 ++++++++ transforms/__tests__/class-test.js | 1 + transforms/class.js | 77 ++++++------ transforms/utils/ReactUtils.js | 116 ++++++++++++------ 5 files changed, 222 insertions(+), 72 deletions(-) create mode 100644 transforms/__testfixtures__/class-anonymous2.input.js create mode 100644 transforms/__testfixtures__/class-anonymous2.output.js diff --git a/transforms/__testfixtures__/class-anonymous2.input.js b/transforms/__testfixtures__/class-anonymous2.input.js new file mode 100644 index 00000000..a9024745 --- /dev/null +++ b/transforms/__testfixtures__/class-anonymous2.input.js @@ -0,0 +1,50 @@ +/** + * @flow + */ +/* eslint-disable no-use-before-define */ +'use strict'; + +var React = require('React'); + +var CrazyObject = { + foo: { + bar: 123, + }, + method: { + wrapThisGuy: (x) => x, + deep: { + wrapThatGuy: (x) => x, + }, + }, + iDontUnderstand: { + whyYouDoThis: { + butAnyway: { + comp1: React.createClass({ + render() { + return
; + }, + }), + comp2: CrazyObject.method.wrapThatGuy(React.createClass({ + render() { + return
; + }, + })), + waitWhatArrayForReal: [React.createClass({ + render() { + return
; + }, + }), [React.createClass({ + render() { + return

; + }, + }), React.createClass({ + render() { + return ; + }, + })]], + }, + }, + }, +}; + +module.exports = WaltUtils; diff --git a/transforms/__testfixtures__/class-anonymous2.output.js b/transforms/__testfixtures__/class-anonymous2.output.js new file mode 100644 index 00000000..1b7a8fcb --- /dev/null +++ b/transforms/__testfixtures__/class-anonymous2.output.js @@ -0,0 +1,50 @@ +/** + * @flow + */ +/* eslint-disable no-use-before-define */ +'use strict'; + +var React = require('React'); + +var CrazyObject = { + foo: { + bar: 123, + }, + method: { + wrapThisGuy: (x) => x, + deep: { + wrapThatGuy: (x) => x, + }, + }, + iDontUnderstand: { + whyYouDoThis: { + butAnyway: { + comp1: class extends React.Component { + render() { + return

; + } + }, + comp2: CrazyObject.method.wrapThatGuy(class extends React.Component { + render() { + return
; + } + }), + waitWhatArrayForReal: [class extends React.Component { + render() { + return
; + } + }, [class extends React.Component { + render() { + return

; + } + }, class extends React.Component { + render() { + return ; + } + }]], + }, + }, + }, +}; + +module.exports = WaltUtils; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 4eb3f59b..b2a149b8 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -21,6 +21,7 @@ const enableFlowOption = {flow: true}; defineTest(__dirname, 'class'); defineTest(__dirname, 'class', enableFlowOption, 'class-anonymous'); +defineTest(__dirname, 'class', enableFlowOption, 'class-anonymous2'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-test2'); defineTest(__dirname, 'class', enableFlowOption, 'export-default-class'); defineTest(__dirname, 'class', pureMixinAlternativeOption, 'class-pure-mixin1'); diff --git a/transforms/class.js b/transforms/class.js index a008d068..6b76dd9e 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -162,7 +162,7 @@ module.exports = (file, api, options) => { const hasNoCallsToDeprecatedAPIs = classPath => { if (checkDeprecatedAPICalls(classPath)) { console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' + 'was skipped because of deprecated API calls. Remove calls to ' + DEPRECATED_APIS.join(', ') + ' in your React component and re-run ' + 'this script.' @@ -186,7 +186,7 @@ module.exports = (file, api, options) => { if (hasInvalidCalls) { console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' + 'was skipped because of API calls that will be removed. Remove calls to `' + DEFAULT_PROPS_FIELD + '` and/or `' + GET_INITIAL_STATE_FIELD + '` in your React component and re-run this script.' @@ -202,7 +202,7 @@ module.exports = (file, api, options) => { ); if (hasArguments) { console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' + 'was skipped because `arguments` was found in your functions. ' + 'Arrow functions do not expose an `arguments` object; ' + 'consider changing to use ES6 spread operator and re-run this script.' @@ -260,14 +260,14 @@ module.exports = (file, api, options) => { }; const isInitialStateConvertible = classPath => { - const specPath = ReactUtils.getReactCreateClassSpec(classPath); + const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); if (!specPath) { return false; } const result = isGetInitialStateConstructorSafe(findGetInitialState(specPath)); if (!result) { console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' + 'was skipped because of potential shadowing issues were found in ' + 'the React component. Rename variable declarations of `props` and/or `context` ' + 'in your `getInitialState` and re-run this script.' @@ -277,7 +277,7 @@ module.exports = (file, api, options) => { }; const canConvertToClass = classPath => { - const specPath = ReactUtils.getReactCreateClassSpec(classPath); + const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); if (!specPath) { return false; } @@ -299,7 +299,7 @@ module.exports = (file, api, options) => { .map(prop => prop.key.name ? prop.key.name : prop.key) .join(', '); console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(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 ' + @@ -311,8 +311,8 @@ module.exports = (file, api, options) => { const areMixinsConvertible = (mixinIdentifierNames, classPath) => { if ( - ReactUtils.hasMixins(classPath) && - !ReactUtils.hasSpecificMixins(classPath, mixinIdentifierNames) + ReactUtils.directlyHasMixinsField(classPath) && + !ReactUtils.directlyHasSpecificMixins(classPath, mixinIdentifierNames) ) { return false; } @@ -1023,34 +1023,40 @@ module.exports = (file, api, options) => { ); }); - const updateToClass = (classPath, type) => { - const specPath = ReactUtils.getReactCreateClassSpec(classPath); - const name = ReactUtils.getComponentName(classPath); + const updateToClass = (classPath) => { + const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); + const name = ReactUtils.directlyGetComponentName(classPath); const statics = collectStatics(specPath); const properties = collectNonStaticProperties(specPath); const comments = getComments(classPath); const getInitialState = findGetInitialState(specPath); - var path; + var path = classPath; + if ( - type == 'moduleExports' || - type == 'exportDefault' || - type == 'anonymousInCallExpression' + classPath.parentPath && + classPath.parentPath.value && + classPath.parentPath.value.type === 'VariableDeclarator' ) { - path = ReactUtils.findReactCreateClassCallExpression(classPath); - } else { - path = j(classPath).closest(j.VariableDeclaration); + // the reason that we need to do this awkward dance here is that + // for things like `var Foo = React.createClass({...})`, we need to + // replace the _entire_ VariableDeclaration with + // `class Foo extends React.Component {...}`. + // it looks scary but since we already know it's a VariableDeclarator + // it's actually safe. + // (VariableDeclaration > declarations > VariableDeclarator > CallExpression) + path = classPath.parentPath.parentPath.parentPath; } const staticProperties = createStaticClassProperties(statics); const baseClassName = pureRenderMixinPathAndBinding && - ReactUtils.hasSpecificMixins(classPath, [pureRenderMixinPathAndBinding.binding]) ? + ReactUtils.directlyHasSpecificMixins(classPath, [pureRenderMixinPathAndBinding.binding]) ? 'PureComponent' : 'Component'; - path.replaceWith( + j(path).replaceWith( createESClass( name, baseClassName, @@ -1071,7 +1077,7 @@ module.exports = (file, api, options) => { // class mixins is an array and only contains the identifier -> true // otherwise -> false const mixinsFilter = (classPath) => { - if (!ReactUtils.hasMixins(classPath)) { + if (!ReactUtils.directlyHasMixinsField(classPath)) { return true; } else if (options['pure-component'] && pureRenderMixinPathAndBinding) { const {binding} = pureRenderMixinPathAndBinding; @@ -1080,13 +1086,18 @@ module.exports = (file, api, options) => { } } console.warn( - file.path + ': `' + ReactUtils.getComponentName(classPath) + '` ' + + file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' + 'was skipped because of inconvertible mixins.' ); + return false; }; - const apply = (path, type) => + // the only time that we can't simply replace the createClass call path + // with a new class is when the parent of that is a variable declaration. + // let's delay it and figure it out later (by looking at `path.parentPath`) + // in `updateToClass`. + const apply = (path) => path .filter(mixinsFilter) .filter(hasNoCallsToDeprecatedAPIs) @@ -1094,18 +1105,11 @@ module.exports = (file, api, options) => { .filter(doesNotUseArguments) .filter(isInitialStateConvertible) .filter(canConvertToClass) - .forEach(classPath => updateToClass(classPath, type)); - - const didTransform = ( - apply(ReactUtils.findReactAnonymousCreateClassInCallExpression(root), 'anonymousInCallExpression') - .size() + - apply(ReactUtils.findReactCreateClass(root), 'var') - .size() + - apply(ReactUtils.findReactCreateClassModuleExports(root), 'moduleExports') - .size() + - apply(ReactUtils.findReactCreateClassExportDefault(root), 'exportDefault') - .size() - ) > 0; + .forEach(updateToClass); + + const didTransform = apply( + ReactUtils.findAllReactCreateClassCalls(root) + ).size() > 0; if (didTransform) { // prune removed requires @@ -1118,7 +1122,6 @@ module.exports = (file, api, options) => { return root.toSource(printOptions); } - } return null; diff --git a/transforms/utils/ReactUtils.js b/transforms/utils/ReactUtils.js index 53498533..c09757a2 100644 --- a/transforms/utils/ReactUtils.js +++ b/transforms/utils/ReactUtils.js @@ -86,23 +86,12 @@ module.exports = function(j) { }, }); - const findReactAnonymousCreateClassInCallExpression = path => - path.find(j.CallExpression, { - arguments: [{ - type: 'CallExpression', - callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, - }], - }); - const getReactCreateClassSpec = classPath => { - var callCollection = findReactCreateClassCallExpression(classPath); - if (callCollection.size() === 0) { - return null; - } - const args = callCollection.get('arguments').value; - if (args) { + const {value} = classPath; + const args = (value.init || value.right || value.declaration).arguments; + if (args && args.length) { const spec = args[0]; - if (spec && spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { + if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { return spec; } } @@ -166,13 +155,47 @@ module.exports = function(j) { // --------------------------------------------------------------------------- // Checks if the React class has mixins - const isMixinProperty = property => property.key.name === '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 getClassExtendReactSpec = classPath => classPath.value.body; + + 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; + + // --------------------------------------------------------------------------- + // Direct methods! (see explanation below) + const findAllReactCreateClassCalls = path => + path.find(j.CallExpression, { + callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION, + }); + + // Mixin Stuff const containSameElements = (ls1, ls2) => { if (ls1.length !== ls2.length) { return false; @@ -184,6 +207,8 @@ module.exports = function(j) { ); }; + const keyNameIsMixins = property => property.key.name === 'mixins'; + const isSpecificMixinsProperty = (property, mixinIdentifierNames) => { const key = property.key; const value = property.value; @@ -197,31 +222,46 @@ module.exports = function(j) { ); }; - const hasSpecificMixins = (classPath, mixinIdentifierNames) => { - const spec = getReactCreateClassSpec(classPath); - return spec && spec.properties.some(prop => isSpecificMixinsProperty(prop, mixinIdentifierNames)); + // These following methods assume that the argument is + // a `React.createClass` call expression. In other words, + // they should only be used with `findAllReactCreateClassCalls`. + const directlyGetCreateClassSpec = classPath => { + if (!classPath || !classPath.value) { + return null; + } + const args = classPath.value.arguments; + if (args && args.length) { + const spec = args[0]; + if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) { + return spec; + } + } + return null; }; - // --------------------------------------------------------------------------- - // Others - const getClassExtendReactSpec = classPath => classPath.value.body; + const directlyGetComponentName = classPath => { + let result = ''; + if ( + classPath.parentPath.value && + classPath.parentPath.value.type === 'VariableDeclarator' + ) { + result = classPath.parentPath.value.id.name; + } + return result; + }; - const createCreateReactClassCallExpression = properties => - j.callExpression( - j.memberExpression( - j.identifier('React'), - j.identifier('createClass'), - false - ), - [j.objectExpression(properties)] - ); + const directlyHasMixinsField = classPath => { + const spec = directlyGetCreateClassSpec(classPath); + return spec && spec.properties.some(keyNameIsMixins); + }; - const getComponentName = - classPath => classPath.node.id && classPath.node.id.name; + const directlyHasSpecificMixins = (classPath, mixinIdentifierNames) => { + const spec = directlyGetCreateClassSpec(classPath); + return spec && spec.properties.some(prop => isSpecificMixinsProperty(prop, mixinIdentifierNames)); + }; return { createCreateReactClassCallExpression, - findReactAnonymousCreateClassInCallExpression, findReactES6ClassDeclaration, findReactCreateClass, findReactCreateClassCallExpression, @@ -231,9 +271,15 @@ module.exports = function(j) { getReactCreateClassSpec, getClassExtendReactSpec, hasMixins, - hasSpecificMixins, hasModule, hasReact, isMixinProperty, + + // "direct" methods + findAllReactCreateClassCalls, + directlyGetComponentName, + directlyGetCreateClassSpec, + directlyHasMixinsField, + directlyHasSpecificMixins, }; }; From b65424c93665fd3ee285605658127b83159677f1 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 7 Jul 2016 13:02:23 -0700 Subject: [PATCH 59/64] fixed default and rest params --- npm-shrinkwrap.json | 404 ++++++++++---------- transforms/__testfixtures__/class.input.js | 27 ++ transforms/__testfixtures__/class.output.js | 32 ++ transforms/class.js | 23 +- 4 files changed, 279 insertions(+), 207 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 41f62b03..57c473f7 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -135,9 +135,9 @@ "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz" }, "ast-types": { - "version": "0.8.12", - "from": "ast-types@0.8.12", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz" + "version": "0.8.15", + "from": "ast-types@0.8.15", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz" }, "async": { "version": "1.5.2", @@ -220,9 +220,9 @@ "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.8.0.tgz" }, "babel-jest": { - "version": "13.0.0", + "version": "13.2.2", "from": "babel-jest@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-13.0.0.tgz" + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-13.2.2.tgz" }, "babel-messages": { "version": "6.8.0", @@ -234,82 +234,10 @@ "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz" }, - "babel-plugin-constant-folding": { - "version": "1.0.1", - "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" - }, - "babel-plugin-dead-code-elimination": { - "version": "1.0.2", - "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" - }, - "babel-plugin-eval": { - "version": "1.0.1", - "from": "babel-plugin-eval@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" - }, - "babel-plugin-inline-environment-variables": { - "version": "1.0.1", - "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" - }, "babel-plugin-jest-hoist": { - "version": "13.0.0", - "from": "babel-plugin-jest-hoist@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-13.0.0.tgz" - }, - "babel-plugin-jscript": { - "version": "1.0.4", - "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" - }, - "babel-plugin-member-expression-literals": { - "version": "1.0.1", - "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" - }, - "babel-plugin-property-literals": { - "version": "1.0.1", - "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" - }, - "babel-plugin-proto-to-assign": { - "version": "1.0.4", - "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz", - "dependencies": { - "lodash": { - "version": "3.10.1", - "from": "lodash@>=3.9.3 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" - } - } - }, - "babel-plugin-react-constant-elements": { - "version": "1.0.3", - "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" - }, - "babel-plugin-react-display-name": { - "version": "1.0.3", - "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" - }, - "babel-plugin-remove-console": { - "version": "1.0.1", - "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" - }, - "babel-plugin-remove-debugger": { - "version": "1.0.1", - "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" - }, - "babel-plugin-runtime": { - "version": "1.0.7", - "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" + "version": "13.2.2", + "from": "babel-plugin-jest-hoist@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-13.2.2.tgz" }, "babel-plugin-syntax-async-functions": { "version": "6.8.0", @@ -466,16 +394,6 @@ "from": "babel-plugin-transform-strict-mode@>=6.8.0 <7.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.8.0.tgz" }, - "babel-plugin-undeclared-variables-check": { - "version": "1.0.2", - "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" - }, - "babel-plugin-undefined-to-void": { - "version": "1.1.6", - "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" - }, "babel-preset-es2015": { "version": "6.9.0", "from": "babel-preset-es2015@>=6.6.0 <7.0.0", @@ -487,9 +405,9 @@ "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-1.0.0.tgz" }, "babel-preset-jest": { - "version": "13.0.0", - "from": "babel-preset-jest@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-13.0.0.tgz" + "version": "13.2.2", + "from": "babel-preset-jest@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-13.2.2.tgz" }, "babel-register": { "version": "6.9.0", @@ -517,9 +435,9 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.11.1.tgz" }, "babylon": { - "version": "6.8.2", + "version": "6.8.4", "from": "babylon@>=6.0.18 <7.0.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.8.2.tgz" + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.8.4.tgz" }, "balanced-match": { "version": "0.4.1", @@ -893,16 +811,9 @@ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz" }, "es5-ext": { - "version": "0.10.11", + "version": "0.10.12", "from": "es5-ext@>=0.10.11 <0.11.0", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz", - "dependencies": { - "es6-symbol": { - "version": "3.0.2", - "from": "es6-symbol@>=3.0.2 <3.1.0", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz" - } - } + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz" }, "es6-iterator": { "version": "2.0.0", @@ -967,9 +878,9 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", "dependencies": { "globals": { - "version": "9.8.0", + "version": "9.9.0", "from": "globals@>=9.2.0 <10.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.8.0.tgz" + "resolved": "https://registry.npmjs.org/globals/-/globals-9.9.0.tgz" }, "user-home": { "version": "2.0.0", @@ -1168,11 +1079,6 @@ "from": "form-data@>=1.0.0-rc3 <1.1.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" }, - "fs-readdir-recursive": { - "version": "0.1.2", - "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" - }, "fs.realpath": { "version": "1.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0", @@ -1431,11 +1337,6 @@ "from": "is-glob@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" }, - "is-integer": { - "version": "1.0.6", - "from": "is-integer@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" - }, "is-my-json-valid": { "version": "2.13.1", "from": "is-my-json-valid@>=2.10.0 <3.0.0", @@ -1529,14 +1430,14 @@ "resolved": "https://registry.npmjs.org/jasmine-check/-/jasmine-check-0.1.5.tgz" }, "jest-changed-files": { - "version": "13.0.0", - "from": "jest-changed-files@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-changed-files@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-13.2.2.tgz" }, "jest-cli": { - "version": "13.0.0", + "version": "13.2.3", "from": "jest-cli@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-13.0.0.tgz", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-13.2.3.tgz", "dependencies": { "lodash.escape": { "version": "4.0.0", @@ -1556,59 +1457,59 @@ } }, "jest-config": { - "version": "13.0.0", - "from": "jest-config@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-13.0.0.tgz" + "version": "13.2.3", + "from": "jest-config@>=13.2.3 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-13.2.3.tgz" }, "jest-environment-jsdom": { - "version": "13.0.0", - "from": "jest-environment-jsdom@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-environment-jsdom@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-13.2.2.tgz" }, "jest-environment-node": { - "version": "13.0.0", - "from": "jest-environment-node@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-environment-node@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-13.2.2.tgz" }, "jest-haste-map": { - "version": "13.0.0", - "from": "jest-haste-map@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-haste-map@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-13.2.2.tgz" }, "jest-jasmine1": { - "version": "13.0.0", - "from": "jest-jasmine1@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-jasmine1/-/jest-jasmine1-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-jasmine1@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-jasmine1/-/jest-jasmine1-13.2.2.tgz" }, "jest-jasmine2": { - "version": "13.0.0", - "from": "jest-jasmine2@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-13.0.0.tgz" + "version": "13.2.3", + "from": "jest-jasmine2@>=13.2.3 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-13.2.3.tgz" }, "jest-mock": { - "version": "13.0.0", - "from": "jest-mock@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-mock@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-13.2.2.tgz" }, "jest-resolve": { - "version": "13.0.0", - "from": "jest-resolve@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-resolve@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-13.2.2.tgz" }, "jest-runtime": { - "version": "13.0.0", - "from": "jest-runtime@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-13.0.0.tgz" + "version": "13.2.3", + "from": "jest-runtime@>=13.2.3 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-13.2.3.tgz" }, "jest-snapshot": { - "version": "13.0.0", - "from": "jest-snapshot@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-13.0.0.tgz" + "version": "13.2.3", + "from": "jest-snapshot@>=13.2.3 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-13.2.3.tgz" }, "jest-util": { - "version": "13.0.0", - "from": "jest-util@>=13.0.0 <14.0.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-13.0.0.tgz" + "version": "13.2.2", + "from": "jest-util@>=13.2.2 <14.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-13.2.2.tgz" }, "jodid25519": { "version": "1.0.2", @@ -1636,24 +1537,139 @@ "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.3.25.tgz", "dependencies": { "ast-types": { - "version": "0.8.17", - "from": "git+https://github.com/keyanzhang/ast-types.git", - "resolved": "git+https://github.com/keyanzhang/ast-types.git#e2d344814be03c5f9934379d17af62a872e70570" + "version": "0.8.12", + "from": "ast-types@0.8.12", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz" }, "babel-core": { "version": "5.8.38", "from": "babel-core@>=5.0.0 <6.0.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.38.tgz", "dependencies": { + "babel-plugin-constant-folding": { + "version": "1.0.1", + "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" + }, + "babel-plugin-dead-code-elimination": { + "version": "1.0.2", + "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" + }, + "babel-plugin-eval": { + "version": "1.0.1", + "from": "babel-plugin-eval@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" + }, + "babel-plugin-inline-environment-variables": { + "version": "1.0.1", + "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" + }, + "babel-plugin-jscript": { + "version": "1.0.4", + "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" + }, + "babel-plugin-member-expression-literals": { + "version": "1.0.1", + "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" + }, + "babel-plugin-property-literals": { + "version": "1.0.1", + "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" + }, + "babel-plugin-proto-to-assign": { + "version": "1.0.4", + "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz" + }, + "babel-plugin-react-constant-elements": { + "version": "1.0.3", + "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" + }, + "babel-plugin-react-display-name": { + "version": "1.0.3", + "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" + }, + "babel-plugin-remove-console": { + "version": "1.0.1", + "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" + }, + "babel-plugin-remove-debugger": { + "version": "1.0.1", + "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" + }, + "babel-plugin-runtime": { + "version": "1.0.7", + "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" + }, + "babel-plugin-undeclared-variables-check": { + "version": "1.0.2", + "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" + }, + "babel-plugin-undefined-to-void": { + "version": "1.1.6", + "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" + }, "babylon": { "version": "5.8.38", "from": "babylon@>=5.8.38 <6.0.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz" }, + "fs-readdir-recursive": { + "version": "0.1.2", + "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" + }, + "is-integer": { + "version": "1.0.6", + "from": "is-integer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" + }, "lodash": { "version": "3.10.1", "from": "lodash@>=3.10.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + }, + "output-file-sync": { + "version": "1.1.2", + "from": "output-file-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz" + }, + "recast": { + "version": "0.10.33", + "from": "recast@0.10.33", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz" + }, + "regenerator": { + "version": "0.8.40", + "from": "regenerator@0.8.40", + "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz" + }, + "regexpu": { + "version": "1.3.0", + "from": "regexpu@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz" + }, + "trim-right": { + "version": "1.0.1", + "from": "trim-right@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + }, + "try-resolve": { + "version": "1.0.1", + "from": "try-resolve@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" } } }, @@ -1672,6 +1688,11 @@ "from": "core-js@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" }, + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + }, "globals": { "version": "6.4.1", "from": "globals@>=6.4.0 <7.0.0", @@ -1690,14 +1711,21 @@ "recast": { "version": "0.11.10", "from": "recast@https://github.com/keyanzhang/recast", - "resolved": "git+https://github.com/keyanzhang/recast.git#c9a839fd620307384ab06e019777ca76c057f522" + "resolved": "git+https://github.com/keyanzhang/recast.git#6be175c9631c30c4cdd348cc0329cc04a907a009", + "dependencies": { + "ast-types": { + "version": "0.8.17", + "from": "git+https://github.com/keyanzhang/ast-types.git", + "resolved": "git+https://github.com/keyanzhang/ast-types.git#e2d344814be03c5f9934379d17af62a872e70570" + } + } } } }, "jsdom": { - "version": "9.3.0", + "version": "9.4.1", "from": "jsdom@>=9.2.1 <10.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.3.0.tgz", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.4.1.tgz", "dependencies": { "acorn": { "version": "2.7.0", @@ -1990,9 +2018,9 @@ } }, "loud-rejection": { - "version": "1.5.0", + "version": "1.6.0", "from": "loud-rejection@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.5.0.tgz" + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz" }, "lru-cache": { "version": "4.0.1", @@ -2227,11 +2255,6 @@ "from": "os-tmpdir@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" }, - "output-file-sync": { - "version": "1.1.2", - "from": "output-file-sync@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz" - }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", @@ -2303,9 +2326,9 @@ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" }, "pretty-format": { - "version": "3.3.2", - "from": "pretty-format@>=3.3.0 <4.0.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.3.2.tgz" + "version": "3.4.3", + "from": "pretty-format@>=3.4.3 <4.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.4.3.tgz" }, "private": { "version": "0.1.6", @@ -2373,9 +2396,9 @@ "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz" }, "recast": { - "version": "0.10.33", - "from": "recast@0.10.33", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz", + "version": "0.10.43", + "from": "recast@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz", "dependencies": { "esprima-fb": { "version": "15001.1001.0-dev-harmony-fb", @@ -2406,18 +2429,6 @@ "from": "regenerate@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.1.tgz" }, - "regenerator": { - "version": "0.8.40", - "from": "regenerator@0.8.40", - "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz", - "dependencies": { - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" - } - } - }, "regenerator-runtime": { "version": "0.9.5", "from": "regenerator-runtime@>=0.9.5 <0.10.0", @@ -2428,11 +2439,6 @@ "from": "regex-cache@>=0.4.2 <0.5.0", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" }, - "regexpu": { - "version": "1.3.0", - "from": "regexpu@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz" - }, "regexpu-core": { "version": "2.0.0", "from": "regexpu-core@>=2.0.0 <3.0.0", @@ -2504,9 +2510,9 @@ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz" }, "rimraf": { - "version": "2.5.2", + "version": "2.5.3", "from": "rimraf@>=2.2.8 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz" + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.3.tgz" }, "run-async": { "version": "0.1.0", @@ -2774,16 +2780,6 @@ "from": "trim-newlines@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" }, - "trim-right": { - "version": "1.0.1", - "from": "trim-right@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" - }, - "try-resolve": { - "version": "1.0.1", - "from": "try-resolve@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" - }, "tryit": { "version": "1.0.2", "from": "tryit@>=1.0.1 <2.0.0", @@ -2820,9 +2816,9 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" }, "uglify-js": { - "version": "2.6.4", + "version": "2.7.0", "from": "uglify-js@>=2.6.0 <3.0.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.4.tgz", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.0.tgz", "dependencies": { "async": { "version": "0.2.10", diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index bbb73b28..bcc5561c 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -173,3 +173,30 @@ var mySpec = {}; var NotAnObjectLiteral = React.createClass(mySpec); var WaitWhat = React.createClass(); + +var HasSpreadArgs = React.createClass({ + _helper: function(...args) { + return args; + }, + _helper2: function(a, b, c, ...args) { + return args.concat(a); + }, + _helper3: function(a: number, ...args: Array) { + return args.concat('' + a); + }, + render() { + return

; + }, +}); + +var HasDefaultArgs = React.createClass({ + _helper: function(foo = 12) { + return foo; + }, + _helper2: function({foo: number = 12, abc}, bar: string = 'hey', ...args: Array) { + return args.concat(foo, bar); + }, + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index 2ac5482e..a12ea89a 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -169,3 +169,35 @@ var mySpec = {}; var NotAnObjectLiteral = React.createClass(mySpec); var WaitWhat = React.createClass(); + +class HasSpreadArgs extends React.Component { + _helper = (...args) => { + return args; + }; + + _helper2 = (a, b, c, ...args) => { + return args.concat(a); + }; + + _helper3 = (a: number, ...args: Array) => { + return args.concat('' + a); + }; + + render() { + return
; + } +} + +class HasDefaultArgs extends React.Component { + _helper = (foo = 12) => { + return foo; + }; + + _helper2 = ({foo: number = 12, abc}, bar: string = 'hey', ...args: Array) => { + return args.concat(foo, bar); + }; + + render() { + return
; + } +} diff --git a/transforms/class.js b/transforms/class.js index 6b76dd9e..53492281 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -573,12 +573,29 @@ module.exports = (file, api, options) => { return to; }; - const createArrowFunctionExpression = fn => - copyReturnType(j.arrowFunctionExpression( - fn.params, + const createArrowFunctionExpression = fn => { + const params = [].concat(fn.params); + + if (fn.defaults && fn.defaults.length) { + for (let i = 0; i < fn.defaults.length; i++) { + const val = fn.defaults[i]; + if (val === null) { + continue; + } + params[i] = j.assignmentPattern(params[i], val); + } + } + + if (fn.rest) { // this is flow-parser specific + params.push(j.restElement(fn.rest)); + } + + return copyReturnType(j.arrowFunctionExpression( + params, fn.body, false ), fn); + }; const createArrowProperty = prop => withComments(j.classProperty( From 738558cec4e25d8db6decaf14e75c49419ca7a8c Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 7 Jul 2016 16:29:26 -0700 Subject: [PATCH 60/64] retain top comments when pure-render-mixin is the first node of the body --- .../class-pure-mixin1.input.js | 3 ++- .../class-pure-mixin1.output.js | 1 + .../class-pure-mixin2.input.js | 9 ++++++- .../class-pure-mixin2.output.js | 7 ++++++ .../class-top-comment.input.js | 25 +++++++++++++++++++ .../class-top-comment.output.js | 20 +++++++++++++++ transforms/__tests__/class-test.js | 4 +++ transforms/class.js | 9 +++++++ 8 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 transforms/__testfixtures__/class-top-comment.input.js create mode 100644 transforms/__testfixtures__/class-top-comment.output.js diff --git a/transforms/__testfixtures__/class-pure-mixin1.input.js b/transforms/__testfixtures__/class-pure-mixin1.input.js index 89255327..381a7e7f 100644 --- a/transforms/__testfixtures__/class-pure-mixin1.input.js +++ b/transforms/__testfixtures__/class-pure-mixin1.input.js @@ -1,5 +1,6 @@ -var React = require('React'); +// dont remove me var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); +var React = require('React'); var ComponentWithOnlyPureRenderMixin = React.createClass({ mixins: [ReactComponentWithPureRenderMixin], diff --git a/transforms/__testfixtures__/class-pure-mixin1.output.js b/transforms/__testfixtures__/class-pure-mixin1.output.js index 5fdeef31..741db3c9 100644 --- a/transforms/__testfixtures__/class-pure-mixin1.output.js +++ b/transforms/__testfixtures__/class-pure-mixin1.output.js @@ -1,3 +1,4 @@ +// dont remove me var React = require('React'); class ComponentWithOnlyPureRenderMixin extends React.PureComponent { diff --git a/transforms/__testfixtures__/class-pure-mixin2.input.js b/transforms/__testfixtures__/class-pure-mixin2.input.js index c09d29a5..02734881 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.input.js +++ b/transforms/__testfixtures__/class-pure-mixin2.input.js @@ -1,5 +1,12 @@ -import React from 'React'; +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule Bar + * @typechecks + * @flow + */ import WhateverYouCallIt from 'react-addons-pure-render-mixin'; +import React from 'React'; import dontPruneMe from 'foobar'; var ComponentWithOnlyPureRenderMixin = React.createClass({ diff --git a/transforms/__testfixtures__/class-pure-mixin2.output.js b/transforms/__testfixtures__/class-pure-mixin2.output.js index 98206728..91b13c6a 100644 --- a/transforms/__testfixtures__/class-pure-mixin2.output.js +++ b/transforms/__testfixtures__/class-pure-mixin2.output.js @@ -1,3 +1,10 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule Bar + * @typechecks + * @flow + */ import React from 'React'; import dontPruneMe from 'foobar'; diff --git a/transforms/__testfixtures__/class-top-comment.input.js b/transforms/__testfixtures__/class-top-comment.input.js new file mode 100644 index 00000000..05b4c011 --- /dev/null +++ b/transforms/__testfixtures__/class-top-comment.input.js @@ -0,0 +1,25 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule FooBar + * @typechecks + * @flow + */ +var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); +var React = require('React'); + +var ComponentWithOnlyPureRenderMixin = React.createClass({ + mixins: [ReactComponentWithPureRenderMixin], + + getInitialState: function() { + return { + counter: this.props.initialNumber + 1, + }; + }, + + render: function() { + return ( +
{this.state.counter}
+ ); + }, +}); diff --git a/transforms/__testfixtures__/class-top-comment.output.js b/transforms/__testfixtures__/class-top-comment.output.js new file mode 100644 index 00000000..f77f052b --- /dev/null +++ b/transforms/__testfixtures__/class-top-comment.output.js @@ -0,0 +1,20 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule FooBar + * @typechecks + * @flow + */ +var React = require('React'); + +class ComponentWithOnlyPureRenderMixin extends React.PureComponent { + state = { + counter: this.props.initialNumber + 1, + }; + + render() { + return ( +
{this.state.counter}
+ ); + } +} diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index b2a149b8..6e4c321c 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -30,6 +30,10 @@ defineTest(__dirname, 'class', { 'pure-component': true, }, 'class-pure-mixin2'); defineTest(__dirname, 'class', null, 'class-pure-mixin3'); +defineTest(__dirname, 'class', { + ...pureMixinAlternativeOption, + ...enableFlowOption, +}, 'class-top-comment'); defineTest(__dirname, 'class', enableFlowOption, 'class-initial-state'); defineTest(__dirname, 'class', enableFlowOption, 'class-property-field'); defineTest(__dirname, 'class', enableFlowOption, 'class-flow1'); diff --git a/transforms/class.js b/transforms/class.js index 53492281..927083c9 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -26,6 +26,9 @@ module.exports = (file, api, options) => { const root = j(file.source); + // retain top comments + const { comments: topComments } = root.find(j.Program).get('body', 0).node; + const AUTOBIND_IGNORE_KEYS = { componentDidMount: true, componentDidUpdate: true, @@ -1132,8 +1135,14 @@ module.exports = (file, api, options) => { // prune removed requires if (pureRenderMixinPathAndBinding) { const {binding, path} = pureRenderMixinPathAndBinding; + let shouldReinsertComment = false; if (findUnusedVariables(path, binding).size() === 0) { + shouldReinsertComment = path.parentPath.value.indexOf(path.value) === 0; j(path).remove(); + + if (shouldReinsertComment) { + root.get().node.comments = topComments; + } } } From 3956765f1a113d41819b1a5be0ff23e398e190da Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 7 Jul 2016 19:21:14 -0700 Subject: [PATCH 61/64] fixed trailing comma for func rest params; updated recast --- npm-shrinkwrap.json | 271 ++++++++++---------- transforms/__testfixtures__/class.input.js | 12 + transforms/__testfixtures__/class.output.js | 20 ++ transforms/class.js | 33 +-- 4 files changed, 175 insertions(+), 161 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 57c473f7..d1bd92af 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -135,9 +135,9 @@ "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz" }, "ast-types": { - "version": "0.8.15", - "from": "ast-types@0.8.15", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz" + "version": "0.8.12", + "from": "ast-types@0.8.12", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz" }, "async": { "version": "1.5.2", @@ -234,11 +234,83 @@ "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz" }, + "babel-plugin-constant-folding": { + "version": "1.0.1", + "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" + }, + "babel-plugin-dead-code-elimination": { + "version": "1.0.2", + "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" + }, + "babel-plugin-eval": { + "version": "1.0.1", + "from": "babel-plugin-eval@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" + }, + "babel-plugin-inline-environment-variables": { + "version": "1.0.1", + "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" + }, "babel-plugin-jest-hoist": { "version": "13.2.2", "from": "babel-plugin-jest-hoist@>=13.2.2 <14.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-13.2.2.tgz" }, + "babel-plugin-jscript": { + "version": "1.0.4", + "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" + }, + "babel-plugin-member-expression-literals": { + "version": "1.0.1", + "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" + }, + "babel-plugin-property-literals": { + "version": "1.0.1", + "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" + }, + "babel-plugin-proto-to-assign": { + "version": "1.0.4", + "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz", + "dependencies": { + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.9.3 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + } + } + }, + "babel-plugin-react-constant-elements": { + "version": "1.0.3", + "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" + }, + "babel-plugin-react-display-name": { + "version": "1.0.3", + "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" + }, + "babel-plugin-remove-console": { + "version": "1.0.1", + "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" + }, + "babel-plugin-remove-debugger": { + "version": "1.0.1", + "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" + }, + "babel-plugin-runtime": { + "version": "1.0.7", + "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" + }, "babel-plugin-syntax-async-functions": { "version": "6.8.0", "from": "babel-plugin-syntax-async-functions@>=6.8.0 <7.0.0", @@ -394,6 +466,16 @@ "from": "babel-plugin-transform-strict-mode@>=6.8.0 <7.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.8.0.tgz" }, + "babel-plugin-undeclared-variables-check": { + "version": "1.0.2", + "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" + }, + "babel-plugin-undefined-to-void": { + "version": "1.1.6", + "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" + }, "babel-preset-es2015": { "version": "6.9.0", "from": "babel-preset-es2015@>=6.6.0 <7.0.0", @@ -1079,6 +1161,11 @@ "from": "form-data@>=1.0.0-rc3 <1.1.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" }, + "fs-readdir-recursive": { + "version": "0.1.2", + "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" + }, "fs.realpath": { "version": "1.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0", @@ -1337,6 +1424,11 @@ "from": "is-glob@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" }, + "is-integer": { + "version": "1.0.6", + "from": "is-integer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" + }, "is-my-json-valid": { "version": "2.13.1", "from": "is-my-json-valid@>=2.10.0 <3.0.0", @@ -1537,139 +1629,24 @@ "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.3.25.tgz", "dependencies": { "ast-types": { - "version": "0.8.12", - "from": "ast-types@0.8.12", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz" + "version": "0.8.17", + "from": "git+https://github.com/keyanzhang/ast-types.git", + "resolved": "git+https://github.com/keyanzhang/ast-types.git#e2d344814be03c5f9934379d17af62a872e70570" }, "babel-core": { "version": "5.8.38", "from": "babel-core@>=5.0.0 <6.0.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.38.tgz", "dependencies": { - "babel-plugin-constant-folding": { - "version": "1.0.1", - "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" - }, - "babel-plugin-dead-code-elimination": { - "version": "1.0.2", - "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" - }, - "babel-plugin-eval": { - "version": "1.0.1", - "from": "babel-plugin-eval@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" - }, - "babel-plugin-inline-environment-variables": { - "version": "1.0.1", - "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" - }, - "babel-plugin-jscript": { - "version": "1.0.4", - "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" - }, - "babel-plugin-member-expression-literals": { - "version": "1.0.1", - "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" - }, - "babel-plugin-property-literals": { - "version": "1.0.1", - "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" - }, - "babel-plugin-proto-to-assign": { - "version": "1.0.4", - "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz" - }, - "babel-plugin-react-constant-elements": { - "version": "1.0.3", - "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" - }, - "babel-plugin-react-display-name": { - "version": "1.0.3", - "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" - }, - "babel-plugin-remove-console": { - "version": "1.0.1", - "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" - }, - "babel-plugin-remove-debugger": { - "version": "1.0.1", - "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" - }, - "babel-plugin-runtime": { - "version": "1.0.7", - "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" - }, - "babel-plugin-undeclared-variables-check": { - "version": "1.0.2", - "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" - }, - "babel-plugin-undefined-to-void": { - "version": "1.1.6", - "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" - }, "babylon": { "version": "5.8.38", "from": "babylon@>=5.8.38 <6.0.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz" }, - "fs-readdir-recursive": { - "version": "0.1.2", - "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" - }, - "is-integer": { - "version": "1.0.6", - "from": "is-integer@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" - }, "lodash": { "version": "3.10.1", "from": "lodash@>=3.10.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" - }, - "output-file-sync": { - "version": "1.1.2", - "from": "output-file-sync@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz" - }, - "recast": { - "version": "0.10.33", - "from": "recast@0.10.33", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz" - }, - "regenerator": { - "version": "0.8.40", - "from": "regenerator@0.8.40", - "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz" - }, - "regexpu": { - "version": "1.3.0", - "from": "regexpu@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz" - }, - "trim-right": { - "version": "1.0.1", - "from": "trim-right@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" - }, - "try-resolve": { - "version": "1.0.1", - "from": "try-resolve@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" } } }, @@ -1688,11 +1665,6 @@ "from": "core-js@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" }, - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" - }, "globals": { "version": "6.4.1", "from": "globals@>=6.4.0 <7.0.0", @@ -1711,14 +1683,7 @@ "recast": { "version": "0.11.10", "from": "recast@https://github.com/keyanzhang/recast", - "resolved": "git+https://github.com/keyanzhang/recast.git#6be175c9631c30c4cdd348cc0329cc04a907a009", - "dependencies": { - "ast-types": { - "version": "0.8.17", - "from": "git+https://github.com/keyanzhang/ast-types.git", - "resolved": "git+https://github.com/keyanzhang/ast-types.git#e2d344814be03c5f9934379d17af62a872e70570" - } - } + "resolved": "git+https://github.com/keyanzhang/recast.git#5a5fe8b470e37470744a51678c24ef03b4a8c96b" } } }, @@ -2255,6 +2220,11 @@ "from": "os-tmpdir@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" }, + "output-file-sync": { + "version": "1.1.2", + "from": "output-file-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz" + }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", @@ -2396,9 +2366,9 @@ "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz" }, "recast": { - "version": "0.10.43", - "from": "recast@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz", + "version": "0.10.33", + "from": "recast@0.10.33", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz", "dependencies": { "esprima-fb": { "version": "15001.1001.0-dev-harmony-fb", @@ -2429,6 +2399,18 @@ "from": "regenerate@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.1.tgz" }, + "regenerator": { + "version": "0.8.40", + "from": "regenerator@0.8.40", + "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + } + } + }, "regenerator-runtime": { "version": "0.9.5", "from": "regenerator-runtime@>=0.9.5 <0.10.0", @@ -2439,6 +2421,11 @@ "from": "regex-cache@>=0.4.2 <0.5.0", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" }, + "regexpu": { + "version": "1.3.0", + "from": "regexpu@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz" + }, "regexpu-core": { "version": "2.0.0", "from": "regexpu-core@>=2.0.0 <3.0.0", @@ -2780,6 +2767,16 @@ "from": "trim-newlines@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" }, + "trim-right": { + "version": "1.0.1", + "from": "trim-right@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + }, + "try-resolve": { + "version": "1.0.1", + "from": "try-resolve@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" + }, "tryit": { "version": "1.0.2", "from": "tryit@>=1.0.1 <2.0.0", diff --git a/transforms/__testfixtures__/class.input.js b/transforms/__testfixtures__/class.input.js index bcc5561c..96a0d9dc 100644 --- a/transforms/__testfixtures__/class.input.js +++ b/transforms/__testfixtures__/class.input.js @@ -200,3 +200,15 @@ var HasDefaultArgs = React.createClass({ return
; }, }); + +var ManyArgs = React.createClass({ + _helper: function(foo = 12) { + return foo; + }, + _helper2: function({foo: number = 12, abc}, bar: string = 'hey', x: number, y: number, ...args: Array) { + return args.concat(foo, bar); + }, + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class.output.js b/transforms/__testfixtures__/class.output.js index a12ea89a..150c68d6 100644 --- a/transforms/__testfixtures__/class.output.js +++ b/transforms/__testfixtures__/class.output.js @@ -201,3 +201,23 @@ class HasDefaultArgs extends React.Component { return
; } } + +class ManyArgs extends React.Component { + _helper = (foo = 12) => { + return foo; + }; + + _helper2 = ( + {foo: number = 12, abc}, + bar: string = 'hey', + x: number, + y: number, + ...args: Array + ) => { + return args.concat(foo, bar); + }; + + render() { + return
; + } +} diff --git a/transforms/class.js b/transforms/class.js index 927083c9..d941318a 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -571,33 +571,18 @@ module.exports = (file, api, options) => { ]; }; - const copyReturnType = (to, from) => { - to.returnType = from.returnType; - return to; - }; - const createArrowFunctionExpression = fn => { - const params = [].concat(fn.params); - - if (fn.defaults && fn.defaults.length) { - for (let i = 0; i < fn.defaults.length; i++) { - const val = fn.defaults[i]; - if (val === null) { - continue; - } - params[i] = j.assignmentPattern(params[i], val); - } - } - - if (fn.rest) { // this is flow-parser specific - params.push(j.restElement(fn.rest)); - } - - return copyReturnType(j.arrowFunctionExpression( - params, + const arrowFunc = j.arrowFunctionExpression( + fn.params, fn.body, false - ), fn); + ); + + arrowFunc.returnType = fn.returnType; + arrowFunc.defaults = fn.defaults; + arrowFunc.rest = fn.rest; + + return arrowFunc; }; const createArrowProperty = prop => From c39d3f7efca14b5c576084d3d1d13250da1d5124 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Thu, 7 Jul 2016 21:27:19 -0700 Subject: [PATCH 62/64] handle top comments better --- .../class-pure-mixin4.input.js | 34 +++++++++++++++++++ .../class-pure-mixin4.output.js | 30 ++++++++++++++++ transforms/__tests__/class-test.js | 4 +++ transforms/class.js | 27 +++++++++++---- 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 transforms/__testfixtures__/class-pure-mixin4.input.js create mode 100644 transforms/__testfixtures__/class-pure-mixin4.output.js diff --git a/transforms/__testfixtures__/class-pure-mixin4.input.js b/transforms/__testfixtures__/class-pure-mixin4.input.js new file mode 100644 index 00000000..6cc257c7 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin4.input.js @@ -0,0 +1,34 @@ +/** + * Copyright 2004-present Facebook. All rights reserved. + * + * @providesModule HelloGuys + * @fbt {"foo": "bar"} + * @flow + * @typechecks + */ + +'use strict'; + +const React = require('React'); +const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); + +/** + * just a description here + */ +const HelloGuys = React.createClass({ + mixins: [ + ReactComponentWithPureRenderMixin, + ], + + propTypes: {}, + + render(): ReactElement { + return ( +
+ wassup +
+ ); + }, +}); + +module.exports = HelloGuys; diff --git a/transforms/__testfixtures__/class-pure-mixin4.output.js b/transforms/__testfixtures__/class-pure-mixin4.output.js new file mode 100644 index 00000000..0bc2f737 --- /dev/null +++ b/transforms/__testfixtures__/class-pure-mixin4.output.js @@ -0,0 +1,30 @@ +/** + * Copyright 2004-present Facebook. All rights reserved. + * + * @providesModule HelloGuys + * @fbt {"foo": "bar"} + * @flow + * @typechecks + */ + +'use strict'; + +const React = require('React'); + +/** + * just a description here + */ +class HelloGuys extends React.PureComponent { + props: {}; + static propTypes = {}; + + render(): ReactElement { + return ( +
+ wassup +
+ ); + } +} + +module.exports = HelloGuys; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 6e4c321c..0806874a 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -30,6 +30,10 @@ defineTest(__dirname, 'class', { 'pure-component': true, }, 'class-pure-mixin2'); defineTest(__dirname, 'class', null, 'class-pure-mixin3'); +defineTest(__dirname, 'class', { + ...pureMixinAlternativeOption, + ...enableFlowOption, +}, 'class-pure-mixin4'); defineTest(__dirname, 'class', { ...pureMixinAlternativeOption, ...enableFlowOption, diff --git a/transforms/class.js b/transforms/class.js index d941318a..f6bbc530 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -379,7 +379,7 @@ module.exports = (file, api, options) => { const findRequirePathAndBinding = (moduleName) => { let result = null; - const requireStatement = root.find(j.VariableDeclarator, { + const requireCall = root.find(j.VariableDeclarator, { id: {type: 'Identifier'}, init: { callee: {name: 'require'}, @@ -398,13 +398,15 @@ module.exports = (file, api, options) => { result = { path, binding: path.value.specifiers[0].id.name, + type: 'import', }; }); - } else if (requireStatement.size()) { - requireStatement.forEach(path => { + } else if (requireCall.size()) { + requireCall.forEach(path => { result = { path, binding: path.value.id.name, + type: 'require', }; }); } @@ -1119,12 +1121,25 @@ module.exports = (file, api, options) => { if (didTransform) { // prune removed requires if (pureRenderMixinPathAndBinding) { - const {binding, path} = pureRenderMixinPathAndBinding; + const {binding, path, type} = pureRenderMixinPathAndBinding; let shouldReinsertComment = false; if (findUnusedVariables(path, binding).size() === 0) { - shouldReinsertComment = path.parentPath.value.indexOf(path.value) === 0; - j(path).remove(); + var removePath = null; + if (type === 'require') { + const bodyNode = path.parentPath.parentPath.parentPath.value; + const variableDeclarationNode = path.parentPath.parentPath.value; + + removePath = path.parentPath.parentPath; + shouldReinsertComment = bodyNode.indexOf(variableDeclarationNode) === 0; + } else { + const importDeclarationNode = path.value; + const bodyNode = path.parentPath.value; + + removePath = path; + shouldReinsertComment = bodyNode.indexOf(importDeclarationNode) === 0; + } + j(removePath).remove(); if (shouldReinsertComment) { root.get().node.comments = topComments; } From c02901c7789069d6038963a9fb801aeafb658093 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 8 Jul 2016 02:05:27 -0700 Subject: [PATCH 63/64] OtherClass.getDefaultProps() -> OtherClass.defaultProps --- .../class-access-default-props1.input.js | 15 +++++++++++++++ .../class-access-default-props1.output.js | 17 +++++++++++++++++ transforms/__tests__/class-test.js | 4 ++++ transforms/class.js | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 transforms/__testfixtures__/class-access-default-props1.input.js create mode 100644 transforms/__testfixtures__/class-access-default-props1.output.js diff --git a/transforms/__testfixtures__/class-access-default-props1.input.js b/transforms/__testfixtures__/class-access-default-props1.input.js new file mode 100644 index 00000000..3518c8f9 --- /dev/null +++ b/transforms/__testfixtures__/class-access-default-props1.input.js @@ -0,0 +1,15 @@ +/* @flow */ +var React = require('react'); +var OtherClass = require('OtherClass'); + +var NewThing = React.createClass({ + getDefaultProps: OtherClass.getDefaultProps, + getData() { + return OtherClass.getDefaultProps(); + }, + render() { + return
; + }, +}); + +module.exports = NewThing; diff --git a/transforms/__testfixtures__/class-access-default-props1.output.js b/transforms/__testfixtures__/class-access-default-props1.output.js new file mode 100644 index 00000000..c33fc85c --- /dev/null +++ b/transforms/__testfixtures__/class-access-default-props1.output.js @@ -0,0 +1,17 @@ +/* @flow */ +var React = require('react'); +var OtherClass = require('OtherClass'); + +class NewThing extends React.Component { + static defaultProps = OtherClass.defaultProps; + + getData = () => { + return OtherClass.defaultProps; + }; + + render() { + return
; + } +} + +module.exports = NewThing; diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index 0806874a..2442ca95 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -38,6 +38,10 @@ defineTest(__dirname, 'class', { ...pureMixinAlternativeOption, ...enableFlowOption, }, 'class-top-comment'); +defineTest(__dirname, 'class', { + ...pureMixinAlternativeOption, + ...enableFlowOption, +}, 'class-access-default-props1'); defineTest(__dirname, 'class', enableFlowOption, 'class-initial-state'); defineTest(__dirname, 'class', enableFlowOption, 'class-property-field'); defineTest(__dirname, 'class', enableFlowOption, 'class-flow1'); diff --git a/transforms/class.js b/transforms/class.js index f6bbc530..6e1ee901 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -1030,6 +1030,22 @@ module.exports = (file, api, options) => { ); }); + // convert `OtherClass.getDefaultProps()` to `OtherClass.defaultProps` + const updateOtherDefaultPropsAccess = (classPath) => { + j(classPath) + .find(j.CallExpression, { + callee: { + type: 'MemberExpression', + property: { name: 'getDefaultProps' }, + }, + }) + .forEach(path => j(path).replaceWith( + j.memberExpression( + path.value.callee.object, + j.identifier('defaultProps') + ))); + }; + const updateToClass = (classPath) => { const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); const name = ReactUtils.directlyGetComponentName(classPath); @@ -1073,6 +1089,8 @@ module.exports = (file, api, options) => { comments ) ); + + updateOtherDefaultPropsAccess(path); }; if ( From 1e9204b217d7aeda73ea2e3c63b5700182de63f6 Mon Sep 17 00:00:00 2001 From: Keyan Zhang Date: Fri, 8 Jul 2016 12:13:57 -0700 Subject: [PATCH 64/64] updated README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 557e835a..d0aecf7e 100644 --- a/README.md +++ b/README.md @@ -131,12 +131,18 @@ jscodeshift -t react-codemod/transforms/sort-comp.js - Add `return;` after the assignment when the return statement is part of a control flow statement (not a direct child of `getInitialState()`'s body) and not in an inner function declaration 4. Transform all non-lifecycle methods and fields to class property initializers (like `onClick = () => {};`). All your Flow annotations will be preserved - It's actually not necessary to transform all methods to arrow functions (i.e., to bind them), but this behavior is the same as `createClass()` and we can make sure that we won't accidentally break stuff + 5. Rewrite `AnotherClass.getDefaultProps()` to `AnotherClass.defaultProps` 4. Generate Flow annotations from `propTypes` and put it on the class (this only happens when there's `/* @flow */` in your code and `options['flow']` is `true`) - Flow actually understands `propTypes` in `createClass` calls but not ES6 class components. Here the transformation logic is identical to [how](https://github.com/facebook/flow/blob/master/src/typing/statement.ml#L3526) Flow treats `propTypes` - Notice that Flow treats an optional propType as non-nullable - For example, `foo: React.PropTypes.number` is valid when you pass `{}`, `{foo: null}`, or `{foo: undefined}` as props at **runtime**. However, when Flow infers type from a `createClass` call, only `{}` and `{foo: undefined}` are valid; `{foo: null}` is not. Thus the equivalent type annotation in Flow is actually `{foo?: number}`. The question mark on the left hand side indicates `{}` and `{foo: undefined}` are fine, but when `foo` is present it must be a `number` - For `propTypes` fields that can't be recognized by Flow, `$FlowFixMe` will be used +#### Usage +```bash +./node_modules/.bin/jscodeshift -t ./transforms/class.js --mixin-module-name=react-addons-pure-render-mixin --flow=true --pure-component=true --remove-runtime-proptypes=false +``` + ### Recast Options Options to [recast](https://github.com/benjamn/recast)'s printer can be provided