diff --git a/transforms/React-propTypes-to-prop-types.js b/transforms/React-propTypes-to-prop-types.js
new file mode 100644
index 00000000..d6334ec7
--- /dev/null
+++ b/transforms/React-propTypes-to-prop-types.js
@@ -0,0 +1,170 @@
+/**
+ * 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';
+
+// import React from 'react';
+const isReactImport = path => (
+ path.node.specifiers.some(specifier => (
+ specifier.type === 'ImportDefaultSpecifier' &&
+ specifier.local.name === 'React'
+ ))
+);
+
+// const React = require('react');
+const isReactRequire = path => (
+ path.node.callee.type === 'Identifier' &&
+ path.parent.node.type === 'VariableDeclarator' &&
+ (
+ path.parent.node.id.type === 'Identifier' &&
+ path.parent.node.id.name === 'React' ||
+ path.parent.node.id.type === 'ObjectPattern' &&
+ path.parent.node.id.properties.some(
+ property => property.value.name === 'React'
+ )
+ )
+);
+
+// React.PropTypes
+const isReactPropTypes = path => (
+ path.node.name === 'PropTypes' &&
+ path.parent.node.type === 'MemberExpression' &&
+ path.parent.node.object.name === 'React'
+);
+
+// If any PropTypes references exist, add a 'prop-types' import (or require)
+function addPropTypesImport(j, root) {
+ if (useImportSyntax(j, root)) {
+ const importStatement = j.importDeclaration(
+ [j.importDefaultSpecifier(j.identifier('PropTypes'))],
+ j.literal('prop-types')
+ );
+
+ root
+ .find(j.ImportDeclaration)
+ .filter(isReactImport)
+ .forEach(path => {
+ j(path).insertAfter(importStatement);
+ });
+ } else {
+ const requireStatement = useVar(j, root)
+ ? j.template.statement`var PropTypes = require('prop-types');`
+ : j.template.statement`const PropTypes = require('prop-types');`;
+
+ root
+ .find(j.CallExpression, {callee: {name: 'require'}})
+ .filter(isReactRequire)
+ .forEach(path => {
+ j(path.parent.parent).insertAfter(requireStatement);
+ });
+ }
+}
+
+// Remove PropTypes destructure statements (eg const { ProptTypes } = React)
+function removeDestructuredPropTypeStatements(j, root) {
+ let hasModifications = false;
+
+ root
+ .find(j.ObjectPattern)
+ .filter(path => (
+ path.parent.node.init.name === 'React' &&
+ path.node.properties.some(
+ property => property.key.name === 'PropTypes'
+ )
+ ))
+ .forEach(path => {
+ hasModifications = true;
+
+ // Remove the PropTypes key
+ path.node.properties = path.node.properties.filter(
+ property => property.key.name !== 'PropTypes'
+ );
+
+ // If this was the only property, remove the entire statement.
+ if (path.node.properties.length === 0) {
+ path.parent.parent.replace('');
+ }
+ });
+
+ return hasModifications;
+}
+
+// Replace all React.PropTypes instances with PropTypes
+function replacePropTypesReferences(j, root) {
+ let hasModifications = false;
+
+ root
+ .find(j.Identifier)
+ .filter(isReactPropTypes)
+ .forEach(path => {
+ hasModifications = true;
+
+ j(path.parent).replaceWith(
+ j.identifier('PropTypes')
+ );
+ });
+
+ return hasModifications;
+}
+
+function useImportSyntax(j, root) {
+ return root
+ .find(j.CallExpression, {callee: {name: 'require'}})
+ .length === 0;
+}
+
+function useVar(j, root) {
+ return root
+ .find(j.VariableDeclaration, {kind: 'const'})
+ .length === 0;
+}
+
+// Remove old { PropTypes } imports
+function removePropTypesImport(j, root) {
+ let hasModifications = false;
+
+ root
+ .find(j.Identifier)
+ .filter(path => (
+ path.node.name === 'PropTypes' &&
+ path.parent.node.type === 'ImportSpecifier'
+ ))
+ .forEach(path => {
+ hasModifications = true;
+
+ const importDeclaration = path.parent.parent.node;
+ importDeclaration.specifiers = importDeclaration.specifiers.filter(
+ specifier => (
+ !specifier.imported ||
+ specifier.imported.name !== 'PropTypes'
+ )
+ );
+ });
+
+ return hasModifications;
+}
+
+module.exports = function(file, api, options) {
+ const j = api.jscodeshift;
+ const root = j(file.source);
+
+ let hasModifications = false;
+ hasModifications = replacePropTypesReferences(j, root) || hasModifications;
+ hasModifications = removePropTypesImport(j, root) || hasModifications;
+ hasModifications = removeDestructuredPropTypeStatements(j, root) || hasModifications;
+
+ if (hasModifications) {
+ addPropTypesImport(j, root);
+ }
+
+ return hasModifications
+ ? root.toSource({ quote: 'single' })
+ : null;
+};
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.input.js
new file mode 100644
index 00000000..466c1e22
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.input.js
@@ -0,0 +1,17 @@
+import React, { Component, PropTypes } from 'react';
+
+class ClassComponent extends Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return
{this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.output.js
new file mode 100644
index 00000000..21acdaf0
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-and-named-import.output.js
@@ -0,0 +1,19 @@
+import React, { Component } from 'react';
+
+import PropTypes from 'prop-types';
+
+class ClassComponent extends Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.input.js
new file mode 100644
index 00000000..c950c7ec
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.input.js
@@ -0,0 +1,17 @@
+import React from 'React';
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: React.PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: React.PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.output.js
new file mode 100644
index 00000000..7ca68a8f
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/default-import.output.js
@@ -0,0 +1,19 @@
+import React from 'React';
+
+import PropTypes from 'prop-types';
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-import.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-import.input.js
new file mode 100644
index 00000000..41d7403a
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-import.input.js
@@ -0,0 +1,18 @@
+import React from 'React';
+import PropTypes from 'prop-types'
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-import.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-import.output.js
new file mode 100644
index 00000000..e69de29b
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-require.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-require.input.js
new file mode 100644
index 00000000..32d21c42
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-require.input.js
@@ -0,0 +1,18 @@
+const React = require('React');
+const PropTypes = require('prop-types');
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-require.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/no-change-require.output.js
new file mode 100644
index 00000000..e69de29b
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.input.js
new file mode 100644
index 00000000..2db70ad9
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.input.js
@@ -0,0 +1,18 @@
+const React = require('react');
+const { Component, PropTypes } = React;
+
+class ClassComponent extends Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.output.js
new file mode 100644
index 00000000..98e1e1d8
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-multi.output.js
@@ -0,0 +1,21 @@
+const React = require('react');
+const PropTypes = require('prop-types');
+const {
+ Component
+} = React;
+
+class ClassComponent extends Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.input.js
new file mode 100644
index 00000000..c3e42141
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.input.js
@@ -0,0 +1,18 @@
+const React = require('react');
+const { PropTypes } = React;
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.output.js
new file mode 100644
index 00000000..b46acbaa
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require-destructured-only.output.js
@@ -0,0 +1,18 @@
+const React = require('react');
+const PropTypes = require('prop-types');
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require.input.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require.input.js
new file mode 100644
index 00000000..b0bd1ef4
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require.input.js
@@ -0,0 +1,17 @@
+const React = require('React');
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: React.PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: React.PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__testfixtures__/React-propTypes-to-prop-types/require.output.js b/transforms/__testfixtures__/React-propTypes-to-prop-types/require.output.js
new file mode 100644
index 00000000..b1b1cbd0
--- /dev/null
+++ b/transforms/__testfixtures__/React-propTypes-to-prop-types/require.output.js
@@ -0,0 +1,19 @@
+const React = require('React');
+
+const PropTypes = require('prop-types');
+
+class ClassComponent extends React.Component {
+ static propTypes = {
+ text: PropTypes.string.isRequired,
+ };
+ render() {
+ return {this.props.text}
;
+ }
+}
+
+function FunctionalComponent (props) {
+ return {props.text}
;
+}
+FunctionalComponent.propTypes = {
+ text: PropTypes.string.isRequired,
+};
\ No newline at end of file
diff --git a/transforms/__tests__/React-propTypes-to-prop-types-test.js b/transforms/__tests__/React-propTypes-to-prop-types-test.js
new file mode 100644
index 00000000..3382bd0b
--- /dev/null
+++ b/transforms/__tests__/React-propTypes-to-prop-types-test.js
@@ -0,0 +1,34 @@
+/**
+ * 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 tests = [
+ 'default-and-named-import',
+ 'default-import',
+ 'no-change-import',
+ 'no-change-require',
+ 'require-destructured-multi',
+ 'require-destructured-only',
+ 'require',
+];
+
+const defineTest = require('jscodeshift/dist/testUtils').defineTest;
+
+describe('React-propTypes-to-prop-types', () => {
+ tests.forEach(test =>
+ defineTest(
+ __dirname,
+ 'React-propTypes-to-prop-types',
+ null,
+ `React-propTypes-to-prop-types/${ test }`
+ )
+ );
+});