Skip to content

Commit

Permalink
Add option to customize prop-types module path
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Apr 14, 2017
1 parent a19180b commit 7a6bf0c
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 163 deletions.
328 changes: 165 additions & 163 deletions transforms/React-PropTypes-to-prop-types.js
Expand Up @@ -10,187 +10,189 @@

'use strict';

// Find alpha-sorted import that would follow prop-types
function findImportAfterPropTypes(j, root) {
let target, targetName;

root
.find(j.ImportDeclaration)
.forEach(path => {
const name = path.value.source.value.toLowerCase();
if (
name > 'prop-types' &&
(!target || name < targetName)
) {
targetName = name;
target = path;
}
});

return target;
}

// Find alpha-sorted require that would follow prop-types
function findRequireAfterPropTypes(j, root) {
let target, targetName;

root
.find(j.CallExpression, {callee: {name: 'require'}})
.forEach(path => {
const name = path.node.arguments[0].value.toLowerCase();
if (
name > 'prop-types' &&
(!target || name < targetName)
) {
targetName = name;
target = path;
}
});
module.exports = function(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);

return target;
}

// React.PropTypes
const isReactPropTypes = path => (
path.node.name === 'PropTypes' &&
path.parent.node.type === 'MemberExpression' &&
path.parent.node.object.name === 'React'
);

// Program uses ES import syntax
function useImportSyntax(j, root) {
return root
.find(j.ImportDeclaration)
.length > 0;
}

// Program uses var keywords
function useVar(j, root) {
return root
.find(j.VariableDeclaration, {kind: 'const'})
.length === 0;
}

// If any PropTypes references exist, add a 'prop-types' import (or require)
function addPropTypesImport(j, root) {
if (useImportSyntax(j, root)) {
const path = findImportAfterPropTypes(j, root);
if (path) {
const importStatement = j.importDeclaration(
[j.importDefaultSpecifier(j.identifier('PropTypes'))],
j.literal('prop-types')
);
j(path).insertBefore(importStatement);
return;
}
const MODULE_NAME = options['module-name'] || 'prop-types';

// Find alpha-sorted import that would follow prop-types
function findImportAfterPropTypes(j, root) {
let target, targetName;

root
.find(j.ImportDeclaration)
.forEach(path => {
const name = path.value.source.value.toLowerCase();
if (
name > MODULE_NAME &&
(!target || name < targetName)
) {
targetName = name;
target = path;
}
});

return target;
}

const path = findRequireAfterPropTypes(j, root);
if (path) {
const requireStatement = useVar(j, root)
? j.template.statement`var PropTypes = require('prop-types');\n`
: j.template.statement`const PropTypes = require('prop-types');\n`;
j(path.parent.parent).insertBefore(requireStatement);
return;
// Find alpha-sorted require that would follow prop-types
function findRequireAfterPropTypes(j, root) {
let target, targetName;

root
.find(j.CallExpression, {callee: {name: 'require'}})
.forEach(path => {
const name = path.node.arguments[0].value.toLowerCase();
if (
name > MODULE_NAME &&
(!target || name < targetName)
) {
targetName = name;
target = path;
}
});

return target;
}

throw new Error('No PropTypes import found!');
}
// React.PropTypes
const isReactPropTypes = path => (
path.node.name === 'PropTypes' &&
path.parent.node.type === 'MemberExpression' &&
path.parent.node.object.name === 'React'
);

// Program uses ES import syntax
function useImportSyntax(j, root) {
return root
.find(j.ImportDeclaration)
.length > 0;
}

// Remove PropTypes destructure statements (eg const { ProptTypes } = React)
function removeDestructuredPropTypeStatements(j, root) {
let hasModifications = false;
// Program uses var keywords
function useVar(j, root) {
return root
.find(j.VariableDeclaration, {kind: 'const'})
.length === 0;
}

root
.find(j.ObjectPattern)
.filter(path => (
path.parent.node.init &&
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('');
// If any PropTypes references exist, add a 'prop-types' import (or require)
function addPropTypesImport(j, root) {
if (useImportSyntax(j, root)) {
const path = findImportAfterPropTypes(j, root);
if (path) {
const importStatement = j.importDeclaration(
[j.importDefaultSpecifier(j.identifier('PropTypes'))],
j.literal(MODULE_NAME)
);
j(path).insertBefore(importStatement);
return;
}
});
}

const path = findRequireAfterPropTypes(j, root);
if (path) {
const requireStatement = useVar(j, root)
? j.template.statement([`var PropTypes = require('${MODULE_NAME}');\n`])
: j.template.statement([`const PropTypes = require('${MODULE_NAME}');\n`]);
j(path.parent.parent).insertBefore(requireStatement);
return;
}

return hasModifications;
}
throw new Error('No PropTypes import found!');
}

// Remove old { PropTypes } imports
function removePropTypesImport(j, root) {
let hasModifications = false;
// 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 &&
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'
);

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'
)
);
// 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;
return hasModifications;
}

root
.find(j.Identifier)
.filter(isReactPropTypes)
.forEach(path => {
hasModifications = true;

// VariableDeclarator should be removed entirely
// eg 'PropTypes = React.PropTypes'
if (path.parent.parent.node.type === 'VariableDeclarator') {
j(path.parent.parent).remove();
} else {
// MemberExpression should be updated
// eg 'foo = React.PropTypes.string'
j(path.parent).replaceWith(
j.identifier('PropTypes')
// 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;
}
return hasModifications;
}

function removeEmptyReactImport(j, root) {
root
.find(j.ImportDeclaration)
.filter(path => (
path.node.specifiers.length === 0 &&
path.node.source.value === 'react'
))
.replaceWith();
}
// Replace all React.PropTypes instances with PropTypes
function replacePropTypesReferences(j, root) {
let hasModifications = false;

root
.find(j.Identifier)
.filter(isReactPropTypes)
.forEach(path => {
hasModifications = true;

// VariableDeclarator should be removed entirely
// eg 'PropTypes = React.PropTypes'
if (path.parent.parent.node.type === 'VariableDeclarator') {
j(path.parent.parent).remove();
} else {
// MemberExpression should be updated
// eg 'foo = React.PropTypes.string'
j(path.parent).replaceWith(
j.identifier('PropTypes')
);
}
});

return hasModifications;
}

module.exports = function(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);
function removeEmptyReactImport(j, root) {
root
.find(j.ImportDeclaration)
.filter(path => (
path.node.specifiers.length === 0 &&
path.node.source.value === 'react'
))
.replaceWith();
}

let hasModifications = false;
hasModifications = replacePropTypesReferences(j, root) || hasModifications;
Expand Down
@@ -0,0 +1,17 @@
const React = require('React');

class ClassComponent extends React.Component {
static propTypes = {
text: React.PropTypes.string.isRequired,
};
render() {
return <div>{this.props.text}</div>;
}
}

function FunctionalComponent (props) {
return <div>{props.text}</div>;
}
FunctionalComponent.propTypes = {
text: React.PropTypes.string.isRequired,
};
@@ -0,0 +1,18 @@
const PropTypes = require('PropTypes');
const React = require('React');

class ClassComponent extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
};
render() {
return <div>{this.props.text}</div>;
}
}

function FunctionalComponent (props) {
return <div>{props.text}</div>;
}
FunctionalComponent.propTypes = {
text: PropTypes.string.isRequired,
};

0 comments on commit 7a6bf0c

Please sign in to comment.