Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * 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 { basename, extname, dirname } = require('path');
- 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,
- flowObjectCommas: true,
- arrowParensAlways: true,
- arrayBracketSpacing: false,
- objectCurlySpacing: false,
- };
- 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,
- componentWillReceiveProps: true,
- componentWillMount: true,
- componentWillUpdate: true,
- componentWillUnmount: true,
- getChildContext: 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 PURE_MIXIN_MODULE_NAME = options['mixin-module-name'] ||
- 'react-addons-pure-render-mixin';
- const CREATE_CLASS_MODULE_NAME = options['create-class-module-name'] ||
- 'create-react-class';
- const CREATE_CLASS_VARIABLE_NAME = options['create-class-variable-name'] ||
- 'createReactClass';
- const STATIC_KEY = 'statics';
- const STATIC_KEYS = {
- childContextTypes: true,
- contextTypes: true,
- displayName: true,
- propTypes: true,
- };
- const MIXIN_KEY = 'mixins';
- const NO_CONVERSION = options.conversion === false;
- const NO_DISPLAY_NAME = options['display-name'] === false;
- 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;
- }
- });
- }
- }
- // ---------------------------------------------------------------------------
- // 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 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
- );
- const isInitialStateLiftable = getInitialState => {
- if (!getInitialState || !(getInitialState.value)) {
- return true;
- }
- return hasSingleReturnStatement(getInitialState.value);
- };
- // ---------------------------------------------------------------------------
- // 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 hasNoCallsToDeprecatedAPIs = classPath => {
- if (checkDeprecatedAPICalls(classPath)) {
- console.warn(
- 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.'
- );
- return false;
- }
- return true;
- };
- const hasNoRefsToAPIsThatWillBeRemoved = classPath => {
- const hasInvalidCalls = (
- 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.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.'
- );
- return false;
- }
- return true;
- };
- const doesNotUseArguments = classPath => {
- const hasArguments = (
- j(classPath).find(j.Identifier, {name: 'arguments'}).size() > 0
- );
- if (hasArguments) {
- console.warn(
- 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.'
- );
- return false;
- }
- 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 specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
- if (!specPath) {
- return false;
- }
- const result = isGetInitialStateConstructorSafe(findGetInitialState(specPath));
- if (!result) {
- console.warn(
- 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.'
- );
- }
- return result;
- };
- const canConvertToClass = classPath => {
- const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
- if (!specPath) {
- return false;
- }
- const invalidProperties = specPath.properties.filter(prop => (
- !prop.key.name || (
- !STATIC_KEYS.hasOwnProperty(prop.key.name) &&
- STATIC_KEY != prop.key.name &&
- !filterDefaultPropsField(prop) &&
- !filterGetInitialStateField(prop) &&
- !isFunctionExpression(prop) &&
- !isPrimProperty(prop) &&
- !isPrimPropertyWithTypeAnnotation(prop) &&
- MIXIN_KEY != prop.key.name
- )
- ));
- if (invalidProperties.length) {
- const invalidText = invalidProperties
- .map(prop => prop.key.name ? prop.key.name : prop.key)
- .join(', ');
- console.warn(
- 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 ' +
- '`render: foo ? renderA : renderB`.'
- );
- }
- return !invalidProperties.length;
- };
- const areMixinsConvertible = (mixinIdentifierNames, classPath) => {
- if (
- ReactUtils.directlyHasMixinsField(classPath) &&
- !ReactUtils.directlyHasSpecificMixins(classPath, mixinIdentifierNames)
- ) {
- return false;
- }
- return true;
- };
- // ---------------------------------------------------------------------------
- // Collectors
- const pickReturnValueOrCreateIIFE = 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),
- pickReturnValueOrCreateIIFE(prop.value)
- ),
- prop
- );
- // 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 => {
- 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.hasOwnProperty(property.key.name)) {
- result.push(property);
- }
- }
- return result;
- };
- const collectNonStaticProperties = specPath => specPath.properties
- .filter(prop =>
- !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop))
- )
- .filter(prop => (!STATIC_KEYS.hasOwnProperty(prop.key.name)) && prop.key.name !== STATIC_KEY)
- .filter(prop =>
- isFunctionExpression(prop) ||
- isPrimPropertyWithTypeAnnotation(prop) ||
- isPrimProperty(prop)
- );
- const findRequirePathAndBinding = (moduleName) => {
- let result = null;
- const requireCall = root.find(j.VariableDeclarator, {
- id: {type: 'Identifier'},
- init: {
- callee: {name: 'require'},
- arguments: [{value: moduleName}],
- },
- });
- const importStatement = root.find(j.ImportDeclaration, {
- source: {
- value: moduleName,
- },
- });
- if (importStatement.size()) {
- importStatement.forEach(path => {
- result = {
- path,
- binding: path.value.specifiers[0].local.name,
- type: 'import',
- };
- });
- } else if (requireCall.size()) {
- requireCall.forEach(path => {
- result = {
- path,
- binding: path.value.id.name,
- type: 'require',
- };
- });
- }
- return result;
- };
- const pureRenderMixinPathAndBinding = findRequirePathAndBinding(PURE_MIXIN_MODULE_NAME);
- // ---------------------------------------------------------------------------
- // Boom!
- const createMethodDefinition = fn =>
- withComments(j.methodDefinition(
- 'method',
- fn.key,
- fn.value
- ), fn);
- 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);
- // 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
- functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'props'}})
- .forEach(path => j(path).remove());
- functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'context'}})
- .forEach(path => j(path).remove());
- 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 getInitialState's body
- if (getInitialState.value.body.body.indexOf(path.value) === -1) {
- shouldInsertReturnAfterAssignment = true;
- }
- j(path).replaceWith(j.expressionStatement(
- j.assignmentExpression(
- '=',
- j.memberExpression(
- j.thisExpression(),
- j.identifier('state'),
- false
- ),
- path.value.argument
- )
- ));
- if (shouldInsertReturnAfterAssignment) {
- j(path).insertAfter(j.returnStatement(null));
- }
- }).getAST()[0].value.body.body;
- };
- const convertInitialStateToClassProperty = getInitialState =>
- withComments(j.classProperty(
- j.identifier('state'),
- pickReturnValueOrCreateIIFE(getInitialState.value),
- getInitialState.value.returnType,
- false
- ), getInitialState);
- const createConstructorArgs = (hasContextAccess) => {
- if (hasContextAccess) {
- return [j.identifier('props'), j.identifier('context')];
- }
- return [j.identifier('props')];
- };
- 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;
- }
- updatePropsAndContextAccess(getInitialState);
- const constructorArgs = createConstructorArgs(hasContextAccess);
- return [
- createMethodDefinition({
- key: j.identifier('constructor'),
- value: j.functionExpression(
- null,
- constructorArgs,
- j.blockStatement(
- [].concat(
- [
- j.expressionStatement(
- j.callExpression(
- j.identifier('super'),
- constructorArgs
- )
- ),
- ],
- inlineGetInitialState(getInitialState)
- )
- )
- ),
- }),
- ];
- };
- const createArrowFunctionExpression = fn => {
- const arrowFunc = j.arrowFunctionExpression(
- fn.params,
- fn.body,
- false
- );
- arrowFunc.returnType = fn.returnType;
- arrowFunc.defaults = fn.defaults;
- arrowFunc.rest = fn.rest;
- arrowFunc.async = fn.async;
- arrowFunc.generator = fn.generator;
- return arrowFunc;
- };
- 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(prop.key.name),
- prop.value,
- null,
- false
- ), prop);
- const createClassPropertyWithType = prop =>
- withComments(j.classProperty(
- j.identifier(prop.key.name),
- prop.value.expression,
- prop.value.typeAnnotation,
- false
- ), prop);
- // ---------------------------------------------------------------------------
- // Flow!
- const flowAnyType = j.anyTypeAnnotation();
- const flowFixMeType = j.genericTypeAnnotation(
- j.identifier('$FlowFixMe'),
- null
- );
- 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);
- case 'number':
- return j.numberLiteralTypeAnnotation(node.value, node.raw);
- case 'boolean':
- return j.booleanLiteralTypeAnnotation(node.value, node.raw);
- case 'object': // we already know it's a NullLiteral here
- return j.nullLiteralTypeAnnotation();
- default: // this should never happen
- return flowFixMeType;
- }
- };
- const propTypeToFlowMapping = {
- // prim types
- any: flowAnyType,
- array: j.genericTypeAnnotation(
- j.identifier('Array'),
- j.typeParameterInstantiation([flowFixMeType])
- ),
- bool: j.booleanTypeAnnotation(),
- element: flowFixMeType, // flow does the same for `element` type in `propTypes`
- func: j.genericTypeAnnotation(
- j.identifier('Function'),
- null
- ),
- node: flowFixMeType, // flow does the same for `node` type in `propTypes`
- 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),
- shape: (propList) => j.objectTypeAnnotation(propList),
- };
- const propTypeToFlowAnnotation = val => {
- let cursor = val;
- let isOptional = true;
- let typeResult = flowFixMeType;
- if ( // check `.isRequired` first
- cursor.type === 'MemberExpression' &&
- cursor.property.type === 'Identifier' &&
- cursor.property.name === 'isRequired'
- ) {
- isOptional = false;
- cursor = cursor.object;
- }
- 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;
- }
- switch (cursor.callee.property.name) {
- case 'arrayOf': {
- const arg = cursor.arguments[0];
- typeResult = constructor(
- propTypeToFlowAnnotation(arg)[0]
- );
- break;
- }
- case 'instanceOf': {
- const arg = cursor.arguments[0];
- if (arg.type !== 'Identifier') {
- typeResult = flowFixMeType;
- break;
- }
- typeResult = constructor(arg);
- break;
- }
- case 'objectOf': {
- const arg = cursor.arguments[0];
- typeResult = constructor(
- propTypeToFlowAnnotation(arg)[0]
- );
- break;
- }
- case 'oneOf': {
- const argList = cursor.arguments[0].elements;
- if (
- !argList ||
- !argList.every(node =>
- (node.type === 'Literal') ||
- (node.type === 'Identifier' && node.name === 'undefined')
- )
- ) {
- 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';
- const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name;
- const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value);
- flowPropList.push(j.objectTypeProperty(
- keyIsLiteral ? j.literal(name) : j.identifier(name),
- valueType,
- isOptional
- ));
- });
- 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;
- }
- }
- return [typeResult, isOptional];
- };
- const createFlowAnnotationsFromPropTypesProperties = (prop) => {
- const typePropertyList = [];
- if (!prop || prop.value.type !== 'ObjectExpression') {
- return typePropertyList;
- }
- prop.value.properties.forEach(typeProp => {
- if (!typeProp.key) { // stuff like SpreadProperty
- return;
- }
- 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(
- keyIsLiteral ? j.literal(name) : j.identifier(name),
- valueType,
- isOptional
- ));
- });
- return j.classProperty(
- j.identifier('props'),
- null,
- j.typeAnnotation(j.objectTypeAnnotation(typePropertyList)),
- false
- );
- };
- // to ensure that our property initializers' evaluation order is safe
- const repositionStateProperty = (initialStateProperty, propertiesAndMethods) => {
- 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);
- }
- 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.
- // 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
- const createESClass = (
- name,
- baseClassName,
- staticProperties,
- getInitialState,
- rawProperties,
- comments
- ) => {
- const initialStateProperty = [];
- let maybeConstructor = [];
- let maybeFlowStateAnnotation = []; // we only need this when we do `this.state = ...`
- if (isInitialStateLiftable(getInitialState)) {
- if (getInitialState) {
- initialStateProperty.push(convertInitialStateToClassProperty(getInitialState));
- }
- } else {
- maybeConstructor = createConstructor(getInitialState);
- if (shouldTransformFlow) {
- let stateType = j.typeAnnotation(
- j.existsTypeAnnotation()
- );
- if (getInitialState.value.returnType) {
- stateType = getInitialState.value.returnType;
- }
- maybeFlowStateAnnotation.push(j.classProperty(
- j.identifier('state'),
- null,
- stateType,
- false
- ));
- }
- }
- const propertiesAndMethods = rawProperties.map(prop => {
- if (isPrimPropertyWithTypeAnnotation(prop)) {
- return createClassPropertyWithType(prop);
- } else if (isPrimProperty(prop)) {
- return createClassProperty(prop);
- } else if (AUTOBIND_IGNORE_KEYS.hasOwnProperty(prop.key.name)) {
- return createMethodDefinition(prop);
- }
- return createArrowProperty(prop);
- });
- const flowPropsAnnotation = shouldTransformFlow ?
- createFlowAnnotationsFromPropTypesProperties(
- staticProperties.find((path) => path.key.name === 'propTypes')
- ) :
- [];
- 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,
- finalStaticProperties,
- maybeConstructor,
- repositionStateProperty(initialStateProperty, propertiesAndMethods)
- )
- ),
- j.memberExpression(
- j.identifier('React'),
- j.identifier(baseClassName),
- false
- )
- ), {comments});
- };
- const createStaticClassProperty = staticProperty => {
- if (staticProperty.value.type === 'FunctionExpression') {
- return withComments(j.methodDefinition(
- 'method',
- j.identifier(staticProperty.key.name),
- staticProperty.value,
- true
- ), 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,
- null,
- true
- ), staticProperty);
- };
- const createStaticClassProperties = statics =>
- statics.map(createStaticClassProperty);
- 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 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) => {
- 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 = classPath;
- if (
- classPath.parentPath &&
- classPath.parentPath.value &&
- classPath.parentPath.value.type === 'VariableDeclarator'
- ) {
- // 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.directlyHasSpecificMixins(classPath, [pureRenderMixinPathAndBinding.binding]) ?
- 'PureComponent' :
- 'Component';
- j(path).replaceWith(
- createESClass(
- name,
- baseClassName,
- staticProperties,
- getInitialState,
- properties,
- comments
- )
- );
- };
- const addDisplayName = (displayName, specPath) => {
- const props = specPath.properties;
- let safe = true;
- for (let i = 0; i < props.length; i++) {
- const prop = props[i];
- if (prop.key.name === 'displayName') {
- safe = false;
- break;
- }
- }
- if (safe) {
- props.unshift(j.objectProperty(j.identifier('displayName'), j.stringLiteral(displayName)));
- }
- };
- const fallbackToCreateClassModule = (classPath) => {
- const comments = getComments(classPath);
- const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
- if (!NO_DISPLAY_NAME) {
- if (specPath) {
- // Add a displayName property to the spec object
- let path = classPath;
- let displayName;
- while (path && displayName === undefined) {
- switch (path.node.type) {
- case 'ExportDefaultDeclaration':
- displayName = basename(file.path, extname(file.path));
- if (displayName === 'index') {
- // ./{module name}/index.js
- displayName = basename(dirname(file.path));
- }
- break;
- case 'VariableDeclarator':
- displayName = path.node.id.name;
- break;
- case 'AssignmentExpression':
- displayName = path.node.left.name;
- break;
- case 'Property':
- displayName = path.node.key.name;
- break;
- case 'Statement':
- displayName = null;
- break;
- }
- path = path.parent;
- }
- if (displayName) {
- addDisplayName(displayName, specPath);
- }
- }
- }
- withComments(
- j(classPath).replaceWith(
- specPath
- ? j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), [specPath])
- : j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), classPath.value.arguments)
- ),
- {comments},
- );
- };
- 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 is an array and only contains the identifier -> true
- // otherwise -> false
- const mixinsFilter = (classPath) => {
- if (!ReactUtils.directlyHasMixinsField(classPath)) {
- return true;
- } else if (options['pure-component'] && pureRenderMixinPathAndBinding) {
- const {binding} = pureRenderMixinPathAndBinding;
- if (areMixinsConvertible([binding], classPath)) {
- return true;
- }
- }
- console.warn(
- file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
- 'was skipped because of inconvertible mixins.'
- );
- return false;
- };
- const reinsertTopComments = () => {
- root.get().node.comments = topComments;
- };
- let didTransform = false;
- const path = ReactUtils.findAllReactCreateClassCalls(root);
- if (NO_CONVERSION) {
- throw new Error("Unsupported NO_CONVERSION");
- } else {
- // 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`.
- path.forEach(childPath => {
- if (
- mixinsFilter(childPath) &&
- hasNoCallsToDeprecatedAPIs(childPath) &&
- hasNoRefsToAPIsThatWillBeRemoved(childPath) &&
- doesNotUseArguments(childPath) &&
- isInitialStateConvertible(childPath) &&
- canConvertToClass(childPath)
- ) {
- didTransform = true;
- updateToClass(childPath);
- }
- });
- }
- if (didTransform) {
- // prune removed requires
- if (pureRenderMixinPathAndBinding) {
- const {binding, path, type} = pureRenderMixinPathAndBinding;
- let shouldReinsertComment = false;
- if (findUnusedVariables(path, binding).size() === 0) {
- 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) {
- reinsertTopComments();
- }
- }
- }
- }
- }
- return root.toSource(printOptions);
- };
- module.exports.parser = 'flow';
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement